using System.Text;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Tools.PowerPoint;
internal sealed partial record SlideDeckSpec
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SlideDeckSpec).Namespace, nameof(SlideDeckSpec));
///
/// Writes the slide deck as a Markdown outline, which the user reads before creating the file.
///
///
/// The outline is what the Slide Assistant always produced, and it keeps working wherever Markdown
/// does: copying it, exporting it to Word, or taking it into a chat. Slides starting a chapter
/// get a larger heading. Within a slide, the content follows the reading order of its layout, so
/// a heading above a text comes before that text, and the source of a picture after the picture.
///
/// A slide taken from the template says so, and shows what it keeps of the template: all of it
/// when kept unchanged, and every slot the model neither filled nor removed when edited. Such
/// notes of the outline itself are in the language of the app, since the user reads them.
///
/// The template the deck was written for, which gives the reading order and the template slides.
/// The names of the pictures the slides use, by the numbers the model gave them.
/// The outline.
public string ToMarkdownOutline(PresentationTemplate template, IReadOnlyDictionary pictureNames)
{
var outline = new StringBuilder();
outline.Append("# ").Append(this.Title).Append('\n');
foreach (var slide in this.Slides)
{
outline.Append("\n***\n");
var templateSlide = slide.TemplateSlide is { } number ? template.FindSlide(number) : null;
var title = slide.Title.Length > 0 ? slide.Title : templateSlide?.Title ?? string.Empty;
if (title.Length > 0)
outline.Append('\n').Append(slide.StartsChapter ? "## " : "### ").Append(title).Append('\n');
if (templateSlide is null)
{
foreach (var slot in template.FindFamily(slide.Layout)?.Slots ?? [])
AppendSlot(outline, slide, slot, pictureNames);
continue;
}
var templateNote = slide.HasContent ? TB("Slide {0} of the template, edited") : TB("Slide {0} of the template, unchanged");
outline.Append('\n').Append('*').Append(string.Format(templateNote, templateSlide.Number)).Append("*\n");
// A slide on a layout without room for content shows what it holds all the same:
var slots = template.FamilyOf(slide) is not null ? templateSlide.Slots : templateSlide.Contents.Select(content => content.Slot).ToList();
foreach (var slot in slots)
{
if (AppendSlot(outline, slide, slot, pictureNames))
continue;
if (!slide.Removes(slot) && templateSlide.Contents.FirstOrDefault(content => content.Slot.Role == slot.Role && content.Slot.Ordinal == slot.Ordinal) is { } templateContent)
AppendTemplateContent(outline, templateContent);
}
}
return outline.ToString().TrimEnd('\n');
}
/// Whether the slide has content for the slot, which was appended.
private static bool AppendSlot(StringBuilder outline, SlideSpec slide, TemplateSlot slot, IReadOnlyDictionary pictureNames)
{
var index = slot.Ordinal - 1;
switch (slot.Role)
{
case PlaceholderRole.SUBTITLE when slide.Subtitle.Length > 0:
outline.Append('\n').Append('_').Append(slide.Subtitle).Append("_\n");
return true;
case PlaceholderRole.TEXT when index < slide.Texts.Count && slide.Texts[index].Paragraphs.Count > 0:
outline.Append('\n');
foreach (var paragraph in slide.Texts[index].Paragraphs)
outline.Append(' ', 2 * paragraph.Level).Append("- ").Append(paragraph.Text).Append('\n');
return true;
case PlaceholderRole.LABEL when index < slide.Labels.Count && slide.Labels[index].Length > 0:
outline.Append('\n').Append('_').Append(slide.Labels[index]).Append("_\n");
return true;
case PlaceholderRole.PICTURE when index < slide.Pictures.Count:
var number = slide.Pictures[index];
var name = pictureNames.TryGetValue(number, out var pictureName) ? pictureName : $"#{number}";
outline.Append('\n').Append('*').Append(string.Format(TB("Picture: {0}"), name)).Append("*\n");
return true;
case PlaceholderRole.TABLE when index == 0 && slide.Table is { Headers.Count: > 0 } table:
outline.Append('\n');
AppendTableRow(outline, table.Headers);
AppendTableRow(outline, table.Headers.Select(_ => "---").ToList());
foreach (var row in table.Rows)
AppendTableRow(outline, row);
return true;
}
return false;
}
///
/// Shows what a slot of a template slide holds, where the deck keeps it as it is.
///
///
/// The title is the heading of the slide already. The rows of a table become list items, since
/// the template slide tells their cells apart only by " | ".
///
private static void AppendTemplateContent(StringBuilder outline, TemplateSlideContent content)
{
switch (content.Slot.Role)
{
case PlaceholderRole.SUBTITLE or PlaceholderRole.LABEL:
outline.Append('\n').Append('_').Append(content.Text.ReplaceLineEndings(" ")).Append("_\n");
break;
case PlaceholderRole.TEXT or PlaceholderRole.TABLE:
outline.Append('\n');
foreach (var line in content.Text.Split('\n').Select(line => line.Trim()).Where(line => line.Length > 0))
outline.Append("- ").Append(line).Append('\n');
break;
case PlaceholderRole.PICTURE:
outline.Append('\n').Append('*').Append(TB("Picture of the template")).Append("*\n");
break;
}
}
private static void AppendTableRow(StringBuilder outline, IReadOnlyList cells)
{
outline.Append('|');
foreach (var cell in cells)
outline.Append(' ').Append(cell.ReplaceLineEndings(" ").Replace("|", "\\|")).Append(" |");
outline.Append('\n');
}
}