<script lang="ts">
    import {
        Standards,
        StandardsDataSchema,
        type StandardsParams,
        type StandardsData,
    } from "@blade-and-brawn/calculator";
    import {
        Activity,
        Gender,
        lbToKg,
        type Metrics,
    } from "@blade-and-brawn/domain";
    import StandardsTable from "$lib/components/StandardsTable.svelte";
    import { api } from "$lib/api";
    import { Value } from "@sinclair/typebox/value";
    import { onMount } from "svelte";

    type DatasetSummary = { id: string; name: string };
    type Editor = { datasetId: string; name: string; data: StandardsData };

    const NEUTRAL_ACTIVITY_PARAMS = {
        weightModifier: 0,
        weightSkew: 0,
        ageModifier: 0,
        enableGeneration: false,
        difficultyModifier: 0,
        peakAge: 0,
        stretch: { upper: 0, lower: 0 },
    };

    let activity = $state(Activity.BenchPress);
    let metrics = $state({
        gender: Gender.Male,
        age: null as number | null,
        weight: null as number | null, // lb
    });
    let datasets = $state<DatasetSummary[]>([]);

    let editor = $state<Editor | null>(null);
    let savedName = $state<string | null>(null);

    let loadError = $state<string | null>(null);
    let saveError = $state<string | null>(null);
    let saving = $state(false);

    const isDirty = $derived(editor !== null && editor.name !== savedName);

    const neutralParams = $derived.by((): StandardsParams => {
        let maxLevel = 0;
        if (editor) {
            for (const standard of editor.data[activity]?.standards ?? []) {
                for (const key of Object.keys(standard.levels)) {
                    maxLevel = Math.max(maxLevel, Number(key));
                }
            }
        }
        return {
            global: { maxLevel },
            activity: Object.fromEntries(
                Object.values(Activity).map((a) => [
                    a,
                    {
                        ...NEUTRAL_ACTIVITY_PARAMS,
                        stretch: { ...NEUTRAL_ACTIVITY_PARAMS.stretch },
                    },
                ]),
            ) as StandardsParams["activity"],
        };
    });

    const allStandards = $derived(
        editor
            ? new Standards(
                  $state.snapshot({ data: editor.data, params: neutralParams }),
              )
            : null,
    );

    const selectedMetrics = $derived({
        gender: metrics.gender,
        age: metrics.age ?? 0,
        weight: lbToKg(metrics.weight ?? 0),
    } as Metrics);

    function errorMessage(err: unknown): string {
        const value = (err as { value?: { error?: string } })?.value;
        return (
            value?.error ??
            (err instanceof Error ? err.message : "Unknown error")
        );
    }

    async function refreshList() {
        const res = await api.standards.datasets.get();
        if (res.error) throw res.error;
        datasets = res.data;
    }

    async function loadDataset(
        id: string,
        opt: { skipDirtyCheck?: boolean } = {},
    ) {
        if (
            !opt.skipDirtyCheck &&
            isDirty &&
            !confirm("Discard unsaved changes?")
        )
            return;

        saveError = null;
        const res = await api.standards.datasets({ id }).get();
        if (res.error) {
            saveError = errorMessage(res.error);
            return;
        }
        Value.Assert(StandardsDataSchema, res.data.data);

        editor = { datasetId: res.data.id, name: res.data.name, data: res.data.data };
        savedName = res.data.name;
    }

    async function save() {
        if (!editor || !editor.name || !isDirty) return;
        const target = editor;
        saveError = null;
        saving = true;
        try {
            const res = await api.standards.datasets({ id: target.datasetId }).patch({
                name: target.name,
            });
            if (res.error) throw res.error;
            if (editor === target) savedName = target.name;
            await refreshList();
        } catch (err) {
            saveError = errorMessage(err);
        } finally {
            saving = false;
        }
    }

    async function discard() {
        if (!editor) return;
        await loadDataset(editor.datasetId, { skipDirtyCheck: true });
    }

    onMount(async () => {
        try {
            await refreshList();
            const first = datasets[0];
            if (first) await loadDataset(first.id, { skipDirtyCheck: true });
        } catch (err) {
            loadError = errorMessage(err);
        }
    });
</script>

{#if !(editor && allStandards)}
    <div class="w-full flex justify-center pt-10">
        {#if loadError}
            <div role="alert" class="alert alert-error max-w-lg">
                <span>Failed to load datasets: {loadError}</span>
            </div>
        {:else}
            <span class="loading loading-spinner loading-lg"></span>
        {/if}
    </div>
{:else}
    {@const currentEditor = editor}
    <div
        class="w-full px-6 py-2 bg-base-200 border-b border-base-300 flex items-center gap-2 flex-wrap text-sm"
    >
        <span class="label">Dataset: </span>
        <select
            class="select select-sm w-36"
            value={currentEditor.datasetId}
            onchange={async (e) => {
                const select = e.currentTarget;
                await loadDataset(select.value);
                select.value = editor?.datasetId ?? "";
            }}
        >
            {#each datasets as dataset}
                <option value={dataset.id}>{dataset.name || "(untitled)"}</option>
            {/each}
        </select>

        {#if isDirty}
            <span class="badge badge-warning badge-sm">Unsaved</span>
        {/if}

        <div class="flex-1"></div>

        <button
            class="btn btn-ghost btn-sm"
            hidden={!isDirty}
            onclick={discard}
        >
            Discard changes
        </button>
        <button
            class="btn btn-primary btn-sm"
            hidden={!isDirty}
            disabled={saving || !isDirty}
            onclick={save}
        >
            {saving ? "Saving..." : "Save"}
        </button>
    </div>

    {#if saveError}
        <div role="alert" class="alert alert-error w-full mt-4">
            <span>{saveError}</span>
        </div>
    {/if}

    <section class="w-full flex gap-4 flex-wrap mt-6">
        <fieldset class="fieldset bg-base-200 p-4">
            <label class="flex flex-col gap-1">
                <span class="label text-xs">Name</span>
                <input
                    class="input input-sm w-36"
                    bind:value={currentEditor.name}
                    placeholder="Dataset name"
                />
            </label>
            <label class="flex flex-col gap-1">
                <span class="label text-xs">Activity</span>
                <select class="select select-sm w-36" bind:value={activity}>
                    {#each Object.values(Activity) as a}
                        <option value={a}>
                            {allStandards.byActivity(a).getMetadata().name}
                        </option>
                    {/each}
                </select>
            </label>
        </fieldset>
    </section>

    <div class="divider"></div>

    <div class="flex items-center gap-4 text-sm opacity-70">
        <label class="flex items-center gap-2">
            <span class="label">Gender</span>
            <select
                class="select select-sm w-28"
                name="genders"
                bind:value={metrics.gender}
            >
                {#each Object.values(Gender) as gender}
                    <option value={gender}>{gender}</option>
                {/each}
            </select>
        </label>
        <label class="flex items-center gap-2">
            <span class="label">Age</span>
            <input
                class="input input-sm w-20"
                type="number"
                min="0"
                max="100"
                placeholder="18"
                bind:value={metrics.age}
            />
        </label>
        <label class="flex items-center gap-2">
            <span class="label">Weight (lb)</span>
            <input
                class="input input-sm w-20"
                type="number"
                min="0"
                max="600"
                placeholder="170"
                bind:value={metrics.weight}
            />
        </label>
    </div>

    <StandardsTable
        selected={{
            activity,
            metrics: selectedMetrics,
            standardsConfig: { data: currentEditor.data, params: neutralParams },
        }}
        {allStandards}
    />
{/if}
