import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import ApexCharts from 'apexcharts';

/**
 * Resolves a CSS custom property (which may be a `light-dark()` value) to its
 * currently active value for the given scheme. Reading the custom property
 * directly (e.g. via getComputedStyle on an element that just has the var set)
 * returns the raw `light-dark(...)` text, not the resolved color — resolution
 * only happens when the property is used as an actual style value, hence the
 * throwaway probe element.
 */
function resolveColor(varName: string): string {
    const probe = document.createElement('span');
    probe.style.color = `var(${varName})`;
    probe.style.display = 'none';
    document.documentElement.appendChild(probe);
    const resolved = getComputedStyle(probe).color;
    probe.remove();
    return resolved;
}

function isDarkScheme(): boolean {
    return getComputedStyle(document.documentElement).colorScheme.includes('dark');
}

/** Overlays theme-varying option keys with values resolved from the current
 *  --dialect-* CSS tokens, so charts follow the light/dark toggle without a
 *  server round-trip. */
function applyThemeOverlay(options: any): any {
    const borderColor = resolveColor('--dialect-border');
    const inkColor = resolveColor('--dialect-ink');
    const dark = isDarkScheme();

    options.grid = { ...options.grid, borderColor };
    options.legend = {
        ...options.legend,
        labels: { ...options.legend?.labels, colors: inkColor },
    };
    options.tooltip = { ...options.tooltip, theme: dark ? 'dark' : 'light' };

    if (options.xaxis) {
        options.xaxis = {
            ...options.xaxis,
            labels: { ...options.xaxis.labels, style: { ...options.xaxis.labels?.style, colors: inkColor } },
        };
    }
    if (options.yaxis) {
        options.yaxis = {
            ...options.yaxis,
            labels: { ...options.yaxis.labels, style: { ...options.yaxis.labels?.style, colors: inkColor } },
        };
    }

    return options;
}

@customElement('apex-chart')
export class ApexChart extends LitElement {
    private chart?: ApexCharts;
    private lastOptions?: any;
    private lastOptionsJson?: string;
    private destroyTimer?: ReturnType<typeof setTimeout>;

    private readonly onThemeChange = () => {
        if (!this.chart || !this.lastOptions) return;
        this.chart.updateOptions(applyThemeOverlay(this.lastOptions));
    };

    protected createRenderRoot() {
        return this;
    }

    connectedCallback() {
        super.connectedCallback();
        clearTimeout(this.destroyTimer);
        this.destroyTimer = undefined;
        window.addEventListener('dialect-theme-change', this.onThemeChange);
        // Back after a real teardown (Flow detach, cached view): rebuild, since
        // the server only pushes options on setData(), not on re-attach.
        if (!this.chart && this.lastOptionsJson) {
            void this.renderChart(this.lastOptionsJson);
        }
    }

    render() {
        return html`<div id="container" style="width:100%;height:100%"></div>`;
    }

    async renderChart(optionsJson: string) {
        await this.updateComplete;
        this.lastOptionsJson = optionsJson;
        const options = JSON.parse(optionsJson);

        options.chart = {
            ...options.chart,
            events: {
                dataPointSelection: (_e: unknown, _ctx: unknown, cfg: any) => {
                    (this as any).$server.onPointClick(cfg.seriesIndex, cfg.dataPointIndex);
                },
            },
        };

        this.lastOptions = options;
        const themed = applyThemeOverlay(options);

        const container = this.querySelector('#container') as HTMLElement;
        if (this.chart) {
            await this.chart.updateOptions(themed);
        } else {
            this.chart = new ApexCharts(container, themed);
            await this.chart.render();
        }
    }

    disconnectedCallback() {
        super.disconnectedCallback();
        window.removeEventListener('dialect-theme-change', this.onThemeChange);
        // gridstack's _sortDom() re-appends item elements after every
        // move/resize, which disconnects and immediately reconnects this
        // element within the same task — destroying the chart synchronously
        // would blank it with nothing to trigger a re-render. Defer past the
        // current task and bail out if we came back.
        this.destroyTimer = setTimeout(() => {
            if (this.isConnected) return;
            this.chart?.destroy();
            this.chart = undefined;
        }, 0);
    }
}