import { Mark, mergeAttributes, type Extensions, type JSONContent } from '@tiptap/react';
import { StarterKit } from '@tiptap/starter-kit';
import type { TextRun } from './richText';

/**
 * SPIKE — TipTap glue for inline rich text.
 *
 * Bridges the `richRuns` model (what Konva renders) and a constrained TipTap
 * editor (what the user edits): bold + italic + accent colour, single block.
 */

/** Inline colour mark: `<span style="color:…">`. Bold/italic come from StarterKit. */
export const TextColor = Mark.create({
    name: 'textColor',
    addAttributes() {
        return {
            color: {
                default: null as string | null,
                parseHTML: (el: HTMLElement) => el.style.color || null,
                renderHTML: (attrs: { color?: string | null }) =>
                    attrs.color ? { style: `color:${attrs.color}` } : {},
            },
        };
    },
    parseHTML() {
        return [{ style: 'color' }];
    },
    renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
        return ['span', mergeAttributes(HTMLAttributes), 0];
    },
});

/** Minimal extension set: single-block text with bold / italic / colour. */
export function richEditorExtensions(): Extensions {
    return [
        StarterKit.configure({
            heading: false,
            bulletList: false,
            orderedList: false,
            listItem: false,
            blockquote: false,
            codeBlock: false,
            horizontalRule: false,
            strike: false,
            code: false,
            link: false,
        }),
        TextColor,
    ];
}

/** Build a TipTap doc from the plain string + runs. Newlines → paragraphs. */
export function runsToDoc(
    content: string,
    runs: TextRun[] | undefined,
    baseColor: string,
): JSONContent {
    const source: TextRun[] = runs && runs.length > 0 ? runs : [{ text: content }];
    const paragraphs: JSONContent[][] = [[]];

    for (const run of source) {
        const segments = run.text.split('\n');
        segments.forEach((seg, i) => {
            if (i > 0) paragraphs.push([]);
            if (seg.length === 0) return;
            const marks: JSONContent['marks'] = [];
            if (run.bold) marks.push({ type: 'bold' });
            if (run.italic) marks.push({ type: 'italic' });
            if (run.color && run.color.toLowerCase() !== baseColor.toLowerCase()) {
                marks.push({ type: 'textColor', attrs: { color: run.color } });
            }
            paragraphs[paragraphs.length - 1].push({
                type: 'text',
                text: seg,
                ...(marks.length ? { marks } : {}),
            });
        });
    }

    return {
        type: 'doc',
        content: paragraphs.map((p) => ({
            type: 'paragraph',
            ...(p.length ? { content: p } : {}),
        })),
    };
}

/**
 * Serialize a TipTap doc back to `{ content, runs }`. Adjacent runs with
 * identical marks are merged; `runs` is `undefined` when the text is entirely
 * unstyled (so plain layers never carry a redundant runs array).
 */
export function docToContentAndRuns(
    doc: JSONContent,
    baseColor: string,
): { content: string; runs: TextRun[] | undefined } {
    const runs: TextRun[] = [];
    let content = '';
    let styled = false;

    const push = (run: TextRun) => {
        const last = runs[runs.length - 1];
        if (
            last &&
            !!last.bold === !!run.bold &&
            !!last.italic === !!run.italic &&
            (last.color ?? null) === (run.color ?? null)
        ) {
            last.text += run.text;
        } else {
            runs.push({ ...run });
        }
        content += run.text;
    };

    const blocks: JSONContent[] = Array.isArray(doc.content) ? doc.content : [];
    blocks.forEach((block: JSONContent, bi: number) => {
        if (bi > 0) push({ text: '\n' });
        const inline: JSONContent[] = Array.isArray(block.content) ? block.content : [];
        for (const node of inline) {
            if (node.type === 'hardBreak') {
                push({ text: '\n' });
                continue;
            }
            if (node.type !== 'text' || typeof node.text !== 'string') continue;
            const marks = Array.isArray(node.marks) ? node.marks : [];
            const bold = marks.some((m) => m.type === 'bold');
            const italic = marks.some((m) => m.type === 'italic');
            const colorMark = marks.find((m) => m.type === 'textColor');
            const color =
                (colorMark?.attrs?.color as string | undefined) &&
                (colorMark!.attrs!.color as string).toLowerCase() !== baseColor.toLowerCase()
                    ? (colorMark!.attrs!.color as string)
                    : undefined;
            if (bold || italic || color) styled = true;
            push({
                text: node.text,
                ...(bold ? { bold: true } : {}),
                ...(italic ? { italic: true } : {}),
                ...(color ? { color } : {}),
            });
        }
    });

    return { content, runs: styled ? runs : undefined };
}
