/** @jsx m */
/** @jsxFrag m.fragment */
import { WeatherApi, SettingsSpec, SettingField } from "./types.js";
import { checkForError } from "./api.js";
import { MockWeatherApi } from "./mock_api.js";
import { addTest, TestCase } from "./third_party/test.js";

export class SettingsComponent {
    private api: WeatherApi;
    private tenantId: string;
    private account: string;

    private spec: SettingsSpec | null;
    private values: Record<string, any>;
    private loading: boolean;
    private saving: boolean;
    private message: string;
    private messageType: "success" | "error" | "";

    constructor(api: WeatherApi, tenantId: string, account: string) {
        this.api = api;
        this.tenantId = tenantId;
        this.account = account;
        this.spec = null;
        this.values = {};
        this.loading = true;
        this.saving = false;
        this.message = "";
        this.messageType = "";
    }

    oninit(vnode: MithrilVnode): void {
        this.loadSettings();
    }

    private loadSettings(): void {
        this.loading = true;
        this.api.getSettings(this.tenantId, this.account).then((resp) => {
            if (!checkForError(resp)) {
                this.message = resp.error || "unknown error";
                this.messageType = "error";
            } else if (resp.data) {
                this.spec = resp.data.spec;
                this.values = { ...resp.data.values };
            }
            this.loading = false;
            m.redraw();
        });
    }

    private onFieldChange(key: string, value: any): void {
        this.values[key] = value;
    }

    private onSave(): void {
        this.saving = true;
        this.message = "";
        this.api.saveSettings(this.tenantId, this.account, this.values).then((resp) => {
            if (!checkForError(resp)) {
                this.message = resp.error || "save failed";
                this.messageType = "error";
            } else {
                this.message = "Settings saved";
                this.messageType = "success";
            }
            this.saving = false;
            m.redraw();
        });
    }

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

        if (!this.spec) {
            return <div class="error">Failed to load settings</div>;
        }

        const self = this;
        const fields = this.spec.fields.map((field) => {
            return self.renderField(field);
        });

        let messageEl: MithrilRawElement | null = null;
        if (this.message) {
            messageEl = <div class={`settings-message ${this.messageType}`}>{this.message}</div>;
        }

        return (
            <div class="settings-form">
                <h3>Settings</h3>
                {fields}
                <button
                    class="settings-save-btn"
                    onclick={() => self.onSave()}
                    disabled={this.saving}
                >
                    {this.saving ? "Saving..." : "Save"}
                </button>
                {messageEl}
            </div>
        );
    }

    private renderField(field: SettingField): MithrilRawElement {
        switch (field.type) {
            case "radio":
                return this.renderRadioField(field);
            case "number":
                return this.renderNumberField(field);
            case "string":
                return this.renderStringField(field);
        }
    }

    private renderRadioField(field: SettingField): MithrilRawElement {
        const self = this;
        const options = (field.options || []).map((opt) => {
            return (
                <label class="radio-option">
                    <input
                        type="radio"
                        name={field.key}
                        value={opt.value}
                        checked={self.values[field.key] === opt.value}
                        onchange={() => self.onFieldChange(field.key, opt.value)}
                    />
                    {opt.label}
                </label>
            );
        });
        return (
            <fieldset class="settings-field">
                <legend>{field.label}</legend>
                {options}
            </fieldset>
        );
    }

    private renderNumberField(field: SettingField): MithrilRawElement {
        const self = this;
        return (
            <div class="settings-field">
                <label>{field.label}</label>
                <input
                    type="number"
                    value={this.values[field.key]}
                    oninput={(e: any) => {
                        const numVal = parseFloat(e.target.value);
                        if (!isNaN(numVal)) {
                            self.onFieldChange(field.key, numVal);
                        }
                    }}
                />
            </div>
        );
    }

    private renderStringField(field: SettingField): MithrilRawElement {
        const self = this;
        return (
            <div class="settings-field">
                <label>{field.label}</label>
                <input
                    type="text"
                    value={this.values[field.key]}
                    oninput={(e: any) => self.onFieldChange(field.key, e.target.value)}
                />
            </div>
        );
    }
}

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

addTest("SettingsComponent view renders with spec and values", (t: TestCase) => {
    const api = new MockWeatherApi(undefined, {
        data: {
            spec: {
                fields: [
                    { key: "tempUnit", label: "Temperature Unit", type: "radio",
                      options: [{ label: "F", value: "F" }, { label: "C", value: "C" }] },
                    { key: "coldThreshold", label: "Cold", type: "number" },
                    { key: "sourceZip", label: "Zip", type: "string" },
                ],
            },
            values: { tempUnit: "F", coldThreshold: 45, sourceZip: "94086" },
        },
    });
    const component = new SettingsComponent(api, "t1", "jwt");
    component["spec"] = api["settingsResponse"].data!.spec;
    component["values"] = { ...api["settingsResponse"].data!.values };
    component["loading"] = false;
    const result = component.view({} as MithrilVnode);
    t.defined(result);
});

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

addTest("SettingsComponent onFieldChange updates values", (t: TestCase) => {
    const api = new MockWeatherApi();
    const component = new SettingsComponent(api, "t1", "jwt");
    component["values"] = { tempUnit: "F" };
    component["onFieldChange"]("tempUnit", "C");
    t.equals("C", component["values"]["tempUnit"]);
});
