import { Dialogue } from 'ass-compiler';

import { CUE_PROPERTY_MAP, TAG_PROPERTY_MAP, TEXT_STYLE_TAGS, VALID_CUE_PROPS } from './constants';
import { IdGenerator } from './id-generator';
import { ProcessedStyle } from './interfaces';
import {
    diffObj,
    formatHclValue,
    quoteString,
    secondsToMilliseconds,
    transformAssTextToEsl,
} from './utils';

interface EslCueMargins {
    left?: number;
    right?: number;
    top?: number;
    bottom?: number;
}

function calculateEffectiveMargins(
    cueMarginData: Dialogue['margin'] | undefined,
    styleMargins: ProcessedStyle['margins'],
): EslCueMargins {
    const effective: EslCueMargins = {};

    const styleL = styleMargins.MarginL ?? 0;
    const styleR = styleMargins.MarginR ?? 0;
    const styleV = styleMargins.MarginV ?? 0;

    const effL =
        cueMarginData?.left !== null && cueMarginData?.left !== undefined
            ? cueMarginData.left
            : styleL;
    const effR =
        cueMarginData?.right !== null && cueMarginData?.right !== undefined
            ? cueMarginData.right
            : styleR;
    const effV =
        cueMarginData?.vertical !== null && cueMarginData?.vertical !== undefined
            ? cueMarginData.vertical
            : styleV;

    if (effL !== 0) effective.left = effL;
    if (effR !== 0) effective.right = effR;
    if (effV !== 0) {
        effective.top = effV;
        effective.bottom = effV;
    }
    return effective;
}

export function processDialogues(
    dialogues: Dialogue[],
    processedStylesMap: Map<string, ProcessedStyle>,
    motionIdGenerator: IdGenerator,
): { cuesHCL: string; motionsHCL: string; autoGeneratedStylesHCL: string } {
    const cuesHCLParts: string[] = [];
    const motionsHCLParts: string[] = [];
    let autoGeneratedStylesHCL = '';

    for (const cue of dialogues) {
        const fragments = cue.slices?.[0]?.fragments || [];
        const firstFragment = cue.slices?.[0]?.fragments?.[0];
        if (!firstFragment?.text) {
            continue;
        }

        let textContent = firstFragment?.text || '';

        // {\an8\fs48\shad0\fnTimes New Roman\c&H3A3A3A&\3c&HE7ECE9&\move(963,211.4,963,111,18,2061)}Hachimitsu Kültür Festivali\N{\fs60}Ödül Sahipleri
        if (fragments.length > 1) {
            for (let index = fragments.length - 1; index > 0; index--) {
                const fragmentBefore = fragments[index - 1];
                const fragment = fragments[index];

                const autoGenerateStyleDiff = diffObj(fragmentBefore.tag, fragment.tag);

                let generatedName = false;

                for (const [key, value] of Object.entries(autoGenerateStyleDiff)) {
                    const getCorresponding = TAG_PROPERTY_MAP[key];

                    if (!getCorresponding) continue;
                    if (!generatedName) {
                        const autoGenerateStyleName = `auto-generated-${motionIdGenerator.next()}`;
                        autoGeneratedStylesHCL += `style ${quoteString(autoGenerateStyleName)} {\n`;
                        generatedName = true;
                        fragment.text = `<style name="${autoGenerateStyleName}">${fragment.text}</style>`;
                    }

                    autoGeneratedStylesHCL += `    ${getCorresponding} = ${value}\n`;
                }

                if (generatedName) {
                    autoGeneratedStylesHCL += `}`;
                }

                textContent = textContent + fragment.text;
            }
        }

        const cueHCLProps: string[] = [];
        const activeTextStyles = new Set<string>();
        const motionNamesForCue: string[] = [];
        const motionOutNamesForCue: string[] = [];
        let currentPosition: [number, number] | null = null;

        const styleName = cue.style;
        const associatedStyle = processedStylesMap.get(styleName);
        associatedStyle?.features.forEach((feature) => activeTextStyles.add(feature));

        const margins = calculateEffectiveMargins(cue.margin, associatedStyle?.margins || {});
        if (margins.bottom !== undefined) cueHCLProps.push(`    margin_bottom = ${margins.bottom}`);
        if (margins.top !== undefined) cueHCLProps.push(`    margin_top = ${margins.top}`);
        if (margins.left !== undefined) cueHCLProps.push(`    margin_left = ${margins.left}`);
        if (margins.right !== undefined) cueHCLProps.push(`    margin_right = ${margins.right}`);

        for (const [assPropName, assPropValue] of Object.entries(cue)) {
            if (
                !VALID_CUE_PROPS.includes(assPropName) ||
                assPropValue === null ||
                assPropValue === undefined
            )
                continue;

            const eslPropName = CUE_PROPERTY_MAP[assPropName];

            if (eslPropName === 'slices' && fragments.length === 1) {
                const tag = firstFragment.tag;
                if (tag) {
                    if (tag.t) {
                        // \t
                        for (const transform of tag.t) {
                            const motionName = `auto-generated-${motionIdGenerator.next()}`;
                            motionNamesForCue.push(motionName);
                            const delay = secondsToMilliseconds(cue.start) + (transform.t1 || 0);
                            const duration = Math.max(0, (transform.t2 || 0) - (transform.t1 || 0));

                            motionsHCLParts.push(`motion ${quoteString(motionName)} {`);
                            motionsHCLParts.push(`    delay = ${delay}`);
                            motionsHCLParts.push(`    duration = ${duration}`);
                            motionsHCLParts.push(`    easing = "linear"`); // currently hardcoded, fix in the future
                            motionsHCLParts.push(`}`);
                        }
                    }
                    if (tag.i === 1) activeTextStyles.add('Italic');
                    else if (tag.i === 0) activeTextStyles.delete('Italic');
                    if (tag.b === 1) activeTextStyles.add('Bold');
                    else if (tag.b === 0) activeTextStyles.delete('Bold');
                }
            } else if (eslPropName === 'position' && cue.pos) {
                // \pos
                currentPosition = [cue.pos.x, cue.pos.y];
            } else if (eslPropName === 'move' && cue.move) {
                // \move
                currentPosition = [cue.move.x1, cue.move.y1]; // \move start position overrides \pos as per ASS spec

                const motionName = `auto-generated-${motionIdGenerator.next()}`;
                motionNamesForCue.push(motionName);
                //const delay = secondsToMilliseconds(cue.start) + (cue.move.t1 || 0);
                const duration = Math.max(0, (cue.move.t2 || 0) - (cue.move.t1 || 0));

                motionsHCLParts.push(`motion ${quoteString(motionName)} {`);
                motionsHCLParts.push(`    duration = ${duration}`);
                motionsHCLParts.push(`    easing = "linear"`);
                motionsHCLParts.push(`    position = [${cue.move.x2}, ${cue.move.y2}]`);
                motionsHCLParts.push(`}`);
            } else if (eslPropName === 'fade') {
                if (cue.fade.type === 'fad') {
                    const motionName = `auto-generated-${motionIdGenerator.next()}`;

                    cueHCLProps.push(`    opacity = 0`);

                    motionNamesForCue.push(motionName);

                    motionsHCLParts.push(`motion ${quoteString(motionName)} {`);
                    motionsHCLParts.push(`    duration = ${cue.fade.t1}`);
                    motionsHCLParts.push(`    easing = "linear"`);
                    motionsHCLParts.push(`    opacity = 1`);
                    motionsHCLParts.push(`}`);

                    if (cue.fade.t2 > 1) {
                        // some people just want in-fade but not out-fade so they just set t2 to 1. It will be instantaneous so we don't need to add a motion for it.
                        const motionOutName = `auto-generated-${motionIdGenerator.next()}`;
                        motionOutNamesForCue.push(motionOutName);

                        motionsHCLParts.push(`motion ${quoteString(motionOutName)} {`);
                        motionsHCLParts.push(`    duration = ${cue.fade.t2}`);
                        motionsHCLParts.push(`    easing = "linear"`); // Default easing
                        motionsHCLParts.push(`    opacity = 0`);
                        motionsHCLParts.push(`}`);
                    }
                }
            } else if (
                eslPropName !== 'slices' &&
                eslPropName !== 'position' &&
                eslPropName !== 'move'
            ) {
                cueHCLProps.push(
                    `    ${eslPropName} = ${formatHclValue(eslPropName, assPropValue)}`,
                );
            }
        }

        if (motionNamesForCue.length > 0) {
            const motionRefs = motionNamesForCue.map((name) => quoteString(name)).join(', ');
            cueHCLProps.push(`    motion_in = [${motionRefs}]`);
        }

        if (motionOutNamesForCue.length > 0) {
            const motionRefs = motionOutNamesForCue.map((name) => quoteString(name)).join(', ');
            cueHCLProps.push(`    motion_out = [${motionRefs}]`);
        }

        if (currentPosition) {
            cueHCLProps.push(`    position = [${currentPosition[0]}, ${currentPosition[1]}]`);
        }

        if (textContent) {
            let eslText = transformAssTextToEsl(textContent);
            const sortedStyles = Array.from(activeTextStyles).sort();
            for (const styleFeature of sortedStyles) {
                const tagName = TEXT_STYLE_TAGS[styleFeature];
                if (tagName) {
                    eslText = `<${tagName}>${eslText}</${tagName}>`;
                }
            }
            cueHCLProps.push(`    text = ${formatHclValue('text', eslText)}`);
        }

        cuesHCLParts.push(`cue {`, ...cueHCLProps.sort(), `}`); // todo: sort by priority
    }

    return {
        cuesHCL: cuesHCLParts.join('\n'),
        motionsHCL: motionsHCLParts.join('\n'),
        autoGeneratedStylesHCL,
    };
}
