// @ts-check
import fs from 'fs';
import path from 'path';

/** @typedef {{ version?: number, tags?: {name: string, [key: string]: unknown}[], valueSets?: {name: string, [key: string]: unknown}[], globalAttributes?: unknown[] }} CustomElementData */

/** Known needle-engine tag names that should be updated from the source */
const NEEDLE_TAG_NAMES = ['needle-engine', 'needle-menu', 'needle-button'];

/**
 * Merges needle-engine custom element data into an existing custom-elements.json file.
 * Preserves user-defined tags while updating needle-engine specific tags.
 * @param {CustomElementData} sourceData - The needle-engine custom-elements.json data
 * @param {CustomElementData} targetData - The existing project custom-elements.json data
 * @returns {CustomElementData} Merged data
 */
function mergeCustomElementData(sourceData, targetData) {
    const merged = { ...targetData };

    // Ensure basic structure
    merged.version = sourceData.version || targetData.version || 1.1;
    merged.tags = merged.tags || [];
    merged.globalAttributes = merged.globalAttributes || targetData.globalAttributes || [];
    merged.valueSets = merged.valueSets || targetData.valueSets || [];

    // Get source tags (needle-engine tags)
    const sourceTags = sourceData.tags || [];

    // Update or add needle-engine tags
    for (const sourceTag of sourceTags) {
        const existingIndex = merged.tags.findIndex(t => t.name === sourceTag.name);
        if (existingIndex >= 0) {
            // Replace existing needle-engine tag with updated version
            if (NEEDLE_TAG_NAMES.includes(sourceTag.name)) {
                merged.tags[existingIndex] = sourceTag;
            }
            // Otherwise keep existing (user-defined) tag
        } else {
            // Add new tag
            merged.tags.push(sourceTag);
        }
    }

    // Merge valueSets (avoid duplicates by name)
    const sourceValueSets = sourceData.valueSets || [];
    for (const sourceSet of sourceValueSets) {
        const existingIndex = merged.valueSets.findIndex(s => s.name === sourceSet.name);
        if (existingIndex >= 0) {
            merged.valueSets[existingIndex] = sourceSet;
        } else {
            merged.valueSets.push(sourceSet);
        }
    }

    return merged;
}

/**
 * Ensure the repo workspace or .vscode settings include Needle Engine custom HTML data if they exist.
 * Copies custom-elements.json to the project and merges with existing user content.
 * - Copies/merges `custom-elements.json` to project root
 * - Adds `./custom-elements.json` to `.code-workspace settings.html.customData`
 * - Adds `./custom-elements.json` to `.vscode/settings.json html.customData`
 * @param {"build" | "serve"} _command
 * @param {import('../types').needleMeta | null} _config
 * @param {import('../types').userSettings} userSettings
 * @returns {import('vite').Plugin | null}
 */
export function needleCustomElementData(_command, _config, userSettings = {}) {
    // Allow disabling the workspace updater
    if (userSettings?.noCustomElementData === true) return null;

    return {
        name: 'needle:custom-element-data',
        configResolved() {
            try {
                const cwd = process.cwd();

                // Path to source custom-elements.json in node_modules
                const sourceFile = path.join(cwd, 'node_modules', '@needle-tools', 'engine', 'custom-elements.json');
                // Path to target custom-elements.json in project vs code directory
                const targetFile = path.join(cwd, '.vscode', 'custom-elements.json');

                // Copy/merge custom-elements.json to project
                if (fs.existsSync(sourceFile)) {
                    try {
                        const sourceData = /** @type {CustomElementData} */ (JSON.parse(fs.readFileSync(sourceFile, 'utf8')));

                        let targetData = /** @type {CustomElementData} */ ({});
                        if (fs.existsSync(targetFile)) {
                            // Merge with existing file to preserve user content
                            try {
                                targetData = /** @type {CustomElementData} */ (JSON.parse(fs.readFileSync(targetFile, 'utf8')));
                            } catch {
                                targetData = {};
                            }
                        }
                        else {
                            // Ensure .vscode directory exists
                            const vscodeDir = path.dirname(targetFile);
                            if (!fs.existsSync(vscodeDir)) {
                                fs.mkdirSync(vscodeDir);
                            }
                        }

                        const mergedData = mergeCustomElementData(sourceData, targetData);
                        const newContent = JSON.stringify(mergedData, null, 2);

                        // Only write if content changed
                        const existingContent = fs.existsSync(targetFile)
                            ? fs.readFileSync(targetFile, 'utf8')
                            : '';
                        if (newContent !== existingContent) {
                            fs.writeFileSync(targetFile, newContent, 'utf8');
                        }
                    } catch (err) {
                        // Fallback: just copy the file if merge fails
                        try {
                            fs.copyFileSync(sourceFile, targetFile);
                        } catch {
                            // ignore
                        }
                    }
                }

                // Local path for VSCode settings (now points to project root)
                const localCustomDataPath = './custom-elements.json';
                // Old path in node_modules (to remove/replace)
                const oldNodeModulesPath = './node_modules/@needle-tools/engine/custom-elements.json';
                const oldWorkspaceNodeModulesPath = './../node_modules/@needle-tools/engine/custom-elements.json';

                // 1) workspace file(s)
                const files = fs.readdirSync(cwd);
                const workspaceFiles = files.filter(f => f.endsWith('.code-workspace'));
                for (const f of workspaceFiles) {
                    const full = path.join(cwd, f);
                    try {
                        const raw = fs.readFileSync(full, 'utf8');
                        const data = /** @type {{settings?: {'html.customData'?: string[], [key: string]: unknown}}} */ (JSON.parse(raw));

                        // Ensure settings.html.customData contains the local path
                        data.settings = data.settings || {};
                        data.settings['html.customData'] = data.settings['html.customData'] || [];

                        // Remove old node_modules path if present
                        const oldIndex = data.settings['html.customData'].indexOf(oldWorkspaceNodeModulesPath);
                        if (oldIndex >= 0) {
                            data.settings['html.customData'].splice(oldIndex, 1);
                        }

                        // Add local path if not present
                        if (!data.settings['html.customData'].includes(localCustomDataPath)) {
                            data.settings['html.customData'].push(localCustomDataPath);

                            const newRaw = JSON.stringify(data, null, 2);
                            fs.writeFileSync(full, newRaw, 'utf8');
                        }
                    } catch (err) {
                        // ignore
                    }
                }

                // 2) .vscode/settings.json
                const vscodeDir = path.join(cwd, '.vscode');
                const settingsFile = path.join(vscodeDir, 'settings.json');

                if (fs.existsSync(settingsFile)) {
                    try {
                        const rawSettings = fs.readFileSync(settingsFile, 'utf8');
                        /** @type {Record<string, unknown>} */
                        const settings = JSON.parse(rawSettings) || {};
                        const htmlData = /** @type {string[]} */ (settings['html.customData'] || []);
                        settings['html.customData'] = htmlData;

                        // Remove old node_modules path if present
                        const oldIndex = htmlData.indexOf(oldNodeModulesPath);
                        if (oldIndex >= 0) {
                            htmlData.splice(oldIndex, 1);
                        }

                        // Add local path if not present
                        if (!htmlData.includes(localCustomDataPath)) {
                            htmlData.push(localCustomDataPath);

                            // Write back settings.json if changed
                            const newRawSettings = JSON.stringify(settings, null, 2);
                            fs.writeFileSync(settingsFile, newRawSettings, 'utf8');
                        }
                    } catch (err) {
                        // ignore
                    }
                }
            } catch (err) {
                // ignore
            }
        }
    }
}

export default needleCustomElementData;
