/** @jsx m */
/** @jsxFrag m.fragment */
import { WeatherApi, WeatherData, WeatherSample } from "./types.js";
import { checkForError } from "./api.js";
import { getWeatherIcon, triangleAlert, hotAlert, coldAlert } from "./weather_icons.js";
import { MockWeatherApi } from "./mock_api.js";
import { addTest, TestCase } from "./third_party/test.js";

function formatIconName(iconName: string): string {
    if (!iconName) {
        return "";
    }
    return iconName
        .split("-")
        .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
        .join(" ");
}

function hasAlerts(sample: WeatherSample): boolean {
    return sample.alerts !== null && sample.alerts !== undefined && sample.alerts.length > 0;
}

interface ChartColumn {
    x: number;
    dayLabel: string;
    maxY: number;
    minY: number;
}

function pluckField<C, T>(columns: C[], extract: (col: C) => T): T[] {
    return columns.map(extract);
}

function buildPolylinePoints(xs: number[], ys: number[]): string {
    return xs.map((x, i) => `${x},${ys[i]}`).join(" ");
}

function renderTempChart(samples: WeatherSample[], coldThreshold: number, hotThreshold: number, tempUnit: string): MithrilRawElement {
    const svgWidth = 520;
    const svgHeight = 80;
    const marginLeft = 30;
    const marginRight = 0;
    const marginTop = 5;
    const marginBottom = 5;
    const chartWidth = svgWidth - marginLeft - marginRight;
    const chartHeight = svgHeight - marginTop - marginBottom;

    // Scale range based on unit
    const tempMin = tempUnit === "C" ? -20 : 0;
    const tempMax = tempUnit === "C" ? 40 : 100;
    const gridStep = 20;

    // Map temperature to Y coordinate (higher temp = lower Y)
    function tempToY(temp: number): number {
        return marginTop + chartHeight * (1 - (temp - tempMin) / (tempMax - tempMin));
    }

    // Map sample index to X coordinate
    function indexToX(i: number): number {
        if (samples.length <= 1) {
            return marginLeft + chartWidth / 2;
        }
        const xPerSample = chartWidth / samples.length;
        return marginLeft + (i + .5) * xPerSample;
    }

    // Horizontal grid lines at regular intervals
    const gridLines: MithrilRawElement[] = [];
    const yLabels: MithrilRawElement[] = [];
    for (let temp = tempMin; temp <= tempMax; temp += gridStep) {
        const y = tempToY(temp);
        gridLines.push(
            <line class="grid" x1={marginLeft} y1={y} x2={svgWidth - marginRight} y2={y} />
        );
        yLabels.push(
            <text x={marginLeft - 4} y={y +4} text-anchor="end">{temp}&deg;</text>
        );
    }

    const columns: ChartColumn[] = samples.map((s, i) => ({
        x: indexToX(i),
        dayLabel: s.date.substring(0, 3),
        maxY: tempToY(s.tempMax),
        minY: tempToY(s.tempMin),
    }));

    const xs = pluckField(columns, (c) => { return c.x; });

    // const xLabels = columns.map((col) => {
    //     return (
    //         <text x={col.x} y={svgHeight - 5} text-anchor="middle">{col.dayLabel}</text>
    //     );
    // });

    const maxPoints = buildPolylinePoints(xs, pluckField(columns, (c) => { return c.maxY; }));
    const minPoints = buildPolylinePoints(xs, pluckField(columns, (c) => { return c.minY; }));

    const maxdots = columns.map((col) => {
        return (
            <circle class="chart-dot" cx={col.x} cy={col.maxY} r={3.5} />
        );
    });

    const mindots = columns.map((col) => {
        return (
            <circle class="chart-dot" cx={col.x} cy={col.minY} r={3.5} />
        );
    });

    // Background temperature range zones
    const hotZoneY = tempToY(tempMax);
    const hotZoneHeight = tempToY(hotThreshold) - hotZoneY;
    const coldZoneY = tempToY(coldThreshold);
    const coldZoneHeight = tempToY(tempMin) - coldZoneY;
    const chartX = marginLeft;
    const chartFullWidth = svgWidth - marginLeft - marginRight;

    // <line class="axis" x1={marginLeft} y1={marginTop} x2={marginLeft} y2={svgHeight - marginBottom} />
    // <line class="axis" x1={marginLeft} y1={svgHeight - marginBottom} x2={svgWidth - marginRight} y2={svgHeight - marginBottom} />
    // {xLabels}
    return (
        <svg class="temp-chart" viewBox={`0 0 ${svgWidth} ${svgHeight}`} xmlns="http://www.w3.org/2000/svg">
            {hotZoneHeight > 0 ? <rect class="zone-hot" x={chartX} y={hotZoneY} width={chartFullWidth} height={hotZoneHeight} /> : null}
            {coldZoneHeight > 0 ? <rect class="zone-cold" x={chartX} y={coldZoneY} width={chartFullWidth} height={coldZoneHeight} /> : null}
            {gridLines}
            {yLabels}
            <g class="maxtemp">
            <polyline points={maxPoints} />
            {maxdots}
            </g>
            <g class="mintemp"> 
            <polyline points={minPoints} />
            {mindots}
            </g>
        </svg>
    );
}

export class WeatherComponent {
    private api: WeatherApi;
    private tenantId: string;
    private account: string;
    private orderId: string;

    private weatherData: WeatherData | null;
    private errorMessage: string;
    private loading: boolean;
    private selectedColumn: number;

    constructor(api: WeatherApi, tenantId: string, account: string, orderId: string) {
        this.api = api;
        this.tenantId = tenantId;
        this.account = account;
        this.orderId = orderId;
        this.weatherData = null;
        this.errorMessage = "";
        this.loading = true;
        this.selectedColumn = -1;
    }

    oninit(vnode: MithrilVnode) {
        this.loadData();
    }

    loadData(): void {
        this.loading = true;
        this.api.getWeatherData(this.tenantId, this.account, this.orderId).then((resp) => {
            if (!checkForError(resp)) {
                this.errorMessage = resp.error || "unknown error";
            } else {
                this.weatherData = resp.data || null;
            }
            this.loading = false;
            m.redraw();
        });
    }

    private onColumnClick(index: number): void {
        this.selectedColumn = index;
    }

    view(vnode: MithrilVnode): MithrilRawElement {
        if (this.loading) {
            return <div>Loading...</div>;
        }

        if (this.errorMessage) {
            return <div class="error">{this.errorMessage}</div>;
        }

        if (!this.weatherData) {
            return <div class="error">no data</div>;
        }

        const samples = this.weatherData.samples;
        const coldThreshold = this.weatherData.coldThreshold;
        const hotThreshold = this.weatherData.hotThreshold;
        const tempUnit = this.weatherData.tempUnit;
        console.log("shippingDistance:", this.weatherData.shippingDistance, "shippingDays:", this.weatherData.shippingDays);
        const self = this;

        interface ViewColumn {
            index: number;
            colClass: string;
            onclick: () => void;
            dayLabel: string;
            iconTitle: string;
            iconSvg: string;
            tempMax: number;
            tempMin: number;
            isAlert: boolean;
            isHot: boolean;
            isCold: boolean;
            firstAlertEvent: string;
        }

        const viewColumns: ViewColumn[] = samples.map((s, i) => {
            const classes = [`col-${i}`];
            if (i === this.selectedColumn) {
                classes.push("selected");
            }

            const isAlert = hasAlerts(s);
            const isCold = s.tempMin < coldThreshold;
            const isHot = s.tempMax > hotThreshold;

            return {
                index: i,
                colClass: classes.join(" "),
                onclick: () => { self.onColumnClick(i); },
                dayLabel: s.date.substring(0, 3),
                iconTitle: formatIconName(s.icon),
                iconSvg: getWeatherIcon(s.icon),
                tempMax: Math.round(s.tempMax),
                tempMin: Math.round(s.tempMin),
                isAlert: isAlert,
                isHot: isHot,
                isCold: isCold,
                firstAlertEvent: hasAlerts(s) ? s.alerts[0].event : "",
            };
        });

        const dayRow1 = pluckField(viewColumns, (col) => {
            return <div class={col.colClass + " day"} onclick={col.onclick}>{col.dayLabel}</div>;
        });

        const dayRow2 = pluckField(viewColumns, (col) => {
            return <div class={col.colClass + " day2"} onclick={col.onclick}>{col.dayLabel}</div>;
        });

        const iconRow = pluckField(viewColumns, (col) => {
            return (
                <div class={col.colClass + " forecast-icon"} title={col.iconTitle} onclick={col.onclick}>
                    {m.trust(col.iconSvg)}
                </div>
            );
        });

        const tempRow = pluckField(viewColumns, (col) => {
            let highlightElement = null;
            return (
                <div class={col.colClass + " temp-cell"} onclick={col.onclick}>
                    <span class="temp-max">{col.tempMax}&deg;</span>
                    /
                    <span class="temp-min">{col.tempMin}&deg;</span>
                    {highlightElement}
                </div>
            );
        });

        const alertIconRow = pluckField(viewColumns, (col) => {
            const alertIcons: MithrilRawElement[] = [];
            if (col.isAlert) {
                alertIcons.push(
                    <span class="alert-icon">
                        {m.trust(triangleAlert)}
                    </span>
                );
            }
            if (col.isCold) {
                alertIcons.push(
                    <span class="alert-icon" title={`extreme cold - below ${coldThreshold}\u00B0${tempUnit}`}>
                        {m.trust(coldAlert)}
                    </span>
                );
            }
            if (col.isHot) {
                alertIcons.push(
                    <span class="alert-icon" title={`extreme heat - above ${hotThreshold}\u00B0${tempUnit}`}>
                        {m.trust(hotAlert)}
                    </span>
                );
            }
            return (
                <div class={col.colClass} onclick={col.onclick}>
                    {alertIcons}
                </div>
            );
        });

        const alertRow = pluckField(viewColumns, (col) => {
            return (
                <div class={col.colClass} onclick={col.onclick}>
                    {col.firstAlertEvent ? <span>{col.firstAlertEvent}</span> : null}
                </div>
            );
        });

        const overlayRow = pluckField(viewColumns, (col) => {
            return (
                <div class={"chart-overlay " + col.colClass} style={"grid-column: " + (col.index + 1)} />
            );
        });

        let alertPanel: MithrilRawElement | string = "";
        if (this.selectedColumn >= 0 && this.selectedColumn < samples.length) {
            const selected = samples[this.selectedColumn];
            if (hasAlerts(selected)) {
                const alertDetails = selected.alerts.map((a) => (
                    <div class="alert-details">
                        <h4>{a.headline}</h4>
                        <p>{a.description}</p>
                    </div>
                ));
                alertPanel = (
                    <div class="alert-panel">
                        {alertDetails}
                    </div>
                );
            }
        }

        const tempChart = renderTempChart(samples, coldThreshold, hotThreshold, tempUnit);

        return (
            <div>
                <h3>{samples.length}-Day Forecast for {this.weatherData.zipCode}</h3>
                <div class="weather-forecast">
                    {dayRow1}
                    {iconRow}
                    {tempRow}
                    {alertIconRow}
                    {alertRow}
                    <div class="weather-chart">{tempChart}</div>
                    {overlayRow}
                    {dayRow2}
                </div>
                {alertPanel}
            </div>
        );
    }
}

addTest("WeatherComponent view renders with data", (t: TestCase) => {
    const api = new MockWeatherApi({
        data: {
            zipCode: "90210",
            tempUnit: "F",
            coldThreshold: 45,
            hotThreshold: 80,
            shippingDistance: -1,
            shippingDays: -1,
            samples: [
                {
                    date: "Mon Jan 13",
                    tempMin: 32,
                    tempMax: 72,
                    icon: "clear-day",
                    alerts: [
                        { event: "Freeze Warning", headline: "Cold temps", description: "Below freezing" },
                    ],
                },
                {
                    date: "Tue Jan 14",
                    tempMin: 40,
                    tempMax: 65,
                    icon: "rain",
                    alerts: [],
                },
            ],
        },
    });
    const component = new WeatherComponent(api, "t1", "jwt", "order1");
    component["weatherData"] = api["response"].data!;
    component["loading"] = false;
    const result = component.view({} as MithrilVnode);
    t.defined(result);
});

addTest("WeatherComponent view renders with selected alert column", (t: TestCase) => {
    const api = new MockWeatherApi({
        data: {
            zipCode: "90210",
            tempUnit: "F",
            coldThreshold: 45,
            hotThreshold: 80,
            shippingDistance: -1,
            shippingDays: -1,
            samples: [
                {
                    date: "Mon Jan 13",
                    tempMin: 32,
                    tempMax: 72,
                    icon: "clear-day",
                    alerts: [
                        { event: "Freeze Warning", headline: "Cold temps", description: "Below freezing" },
                    ],
                },
            ],
        },
    });
    const component = new WeatherComponent(api, "t1", "jwt", "order1");
    component["weatherData"] = api["response"].data!;
    component["loading"] = false;
    component["selectedColumn"] = 0;
    const result = component.view({} as MithrilVnode);
    t.defined(result);
});

addTest("WeatherComponent view renders loading state", (t: TestCase) => {
    const api = new MockWeatherApi();
    const component = new WeatherComponent(api, "t1", "jwt", "order1");
    const result = component.view({} as MithrilVnode);
    t.defined(result);
});

addTest("WeatherComponent view renders error state", (t: TestCase) => {
    const api = new MockWeatherApi();
    const component = new WeatherComponent(api, "t1", "jwt", "order1");
    component["loading"] = false;
    component["errorMessage"] = "something went wrong";
    const result = component.view({} as MithrilVnode);
    t.defined(result);
});

addTest("formatIconName converts icon names to title case", (t: TestCase) => {
    t.equals("Partly Cloudy Day", formatIconName("partly-cloudy-day"));
    t.equals("Clear Day", formatIconName("clear-day"));
    t.equals("Rain", formatIconName("rain"));
    t.equals("Thunder Showers Night", formatIconName("thunder-showers-night"));
});

addTest("formatIconName handles empty string", (t: TestCase) => {
    t.equals("", formatIconName(""));
});

addTest("onColumnClick sets selectedColumn", (t: TestCase) => {
    const api = new MockWeatherApi();
    const component = new WeatherComponent(api, "t1", "jwt", "order1");
    t.equals(-1, component["selectedColumn"]);
    component["onColumnClick"](2);
    t.equals(2, component["selectedColumn"]);
    component["onColumnClick"](3);
    t.equals(3, component["selectedColumn"]);
    component["onColumnClick"](1);
    t.equals(1, component["selectedColumn"]);
});

addTest("renderTempChart renders without error", (t: TestCase) => {
    const samples = [
        { date: "Mon Jan 13", tempMin: 32, tempMax: 72, icon: "clear-day", alerts: [] },
        { date: "Tue Jan 14", tempMin: 40, tempMax: 65, icon: "rain", alerts: [] },
        { date: "Wed Jan 15", tempMin: 28, tempMax: 55, icon: "snow", alerts: [] },
    ];
    const result = renderTempChart(samples, 45, 80, "F");
    t.defined(result);
});

addTest("renderTempChart renders with single sample", (t: TestCase) => {
    const samples = [
        { date: "Mon Jan 13", tempMin: 50, tempMax: 80, icon: "clear-day", alerts: [] },
    ];
    const result = renderTempChart(samples, 45, 80, "F");
    t.defined(result);
});

addTest("renderTempChart renders with Celsius", (t: TestCase) => {
    const samples = [
        { date: "Mon Jan 13", tempMin: 5, tempMax: 25, icon: "clear-day", alerts: [] },
    ];
    const result = renderTempChart(samples, 7, 27, "C");
    t.defined(result);
});

addTest("checkForError returns true on success", (t: TestCase) => {
    t.true(checkForError({ data: { zipCode: "90210", samples: [], tempUnit: "F", coldThreshold: 45, hotThreshold: 80, shippingDistance: -1, shippingDays: -1 } }));
});

addTest("checkForError returns false on error", (t: TestCase) => {
    t.false(checkForError({ error: "something broke" }));
});
