<script lang="ts">
/**
 * Self-contained Data Store edit modal.
 * Handles form fields, change detection, save/delete API calls, and IDB cache update.
 *
 * Usage:
 *   <AE_DataStore_Form bind:open editing_obj={obj_or_null} on_saved={...} on_deleted={...} />
 *
 * - editing_obj = null  → new record (Create mode)
 * - editing_obj = obj   → edit existing record
 * - show_account_field  → show/hide the Account ID text field (hide in embedded widget context)
 * - show_for_fields     → show/hide For Type + For ID (hide in embedded widget context)
 * - default_account_id  → pre-fill account_id when creating a new record
 */
import { untrack } from 'svelte';
import { Modal } from 'flowbite-svelte';
import { Check, ChevronDown, ChevronUp, Code, Eye, LoaderCircle, Save, Trash2, X } from '@lucide/svelte';
import type { ae_DataStore } from '$lib/types/ae_types';
import { api } from '$lib/ae_api/api';
import { ae_api } from '$lib/stores/ae_stores';
import { db_core } from '$lib/ae_core/db_core';
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
import AE_Comp_Editor_TipTap from '$lib/elements/element_editor_tiptap.svelte';

interface Props {
    open: boolean;
    editing_obj?: ae_DataStore | null;   // null/undefined = new record
    show_account_field?: boolean;         // show Account ID field; false for embedded widget
    show_for_fields?: boolean;            // show For Type + For ID; false for embedded widget
    readonly_code?: boolean;              // lock the code field
    default_account_id?: string;          // pre-fill account_id on new records
    on_saved?: (result: Record<string, unknown>) => void; // called after a successful save or create
    on_deleted?: () => void;              // called after a successful delete
}

let {
    open             = $bindable(false),
    editing_obj      = null,
    show_account_field = true,
    show_for_fields    = true,
    readonly_code      = false,
    default_account_id = '',
    on_saved,
    on_deleted
}: Props = $props();

let is_new = $derived(!editing_obj?.id);

// Draft state — initialized from editing_obj each time the modal opens
let draft_code     = $state('');
let draft_name     = $state('');
let draft_type     = $state('text');
let draft_value    = $state('');
let draft_account_id = $state('');
let draft_for_type = $state('');
let draft_for_id   = $state('');
let draft_enable   = $state(true);
let draft_hide     = $state(false);
let draft_priority = $state(false);
let draft_sort     = $state('');
let draft_group    = $state('');
let draft_notes    = $state('');

// Internal UI state — reset each time the modal opens
let html_edit_mode = $state<'source' | 'visual'>('source');
let show_advanced  = $state(false);
let submit_status  = $state<'idle' | 'processing' | 'saved' | 'error'>('idle');

// Initialize all drafts whenever the modal opens
$effect(() => {
    if (open) {
        untrack(() => {
            const obj = editing_obj;
            draft_code       = obj?.code     ?? '';
            draft_name       = obj?.name     ?? '';
            draft_type       = obj?.type     ?? 'text';
            draft_value      = obj
                ? (obj.type === 'json'
                    ? (typeof obj.json === 'string' ? obj.json : JSON.stringify(obj.json ?? '', null, 2))
                    : (obj.text || obj.html || ''))
                : '';
            draft_account_id = obj?.account_id ?? (is_new ? default_account_id : '');
            draft_for_type   = obj?.for_type ?? '';
            draft_for_id     = obj?.for_id   ?? '';
            draft_enable     = obj?.enable !== false;
            draft_hide       = !!obj?.hide;
            draft_priority   = !!obj?.priority;
            draft_sort       = String(obj?.sort ?? '');
            draft_group      = obj?.group   ?? '';
            draft_notes      = obj?.notes   ?? '';
            html_edit_mode   = 'source';
            show_advanced    = false;
            submit_status    = 'idle';
        });
    }
});

// Change detection — compare each draft against editing_obj; new records are always dirty
let has_changes = $derived.by(() => {
    if (!editing_obj?.id) return true;
    const obj = editing_obj;
    const orig_value = obj.type === 'json'
        ? (typeof obj.json === 'string' ? obj.json : JSON.stringify(obj.json ?? '', null, 2))
        : (obj.text || obj.html || '');
    return (
        draft_code       !== (obj.code       ?? '')     ||
        draft_name       !== (obj.name       ?? '')     ||
        draft_type       !== (obj.type       ?? 'text') ||
        draft_value      !== orig_value                 ||
        draft_account_id !== (obj.account_id ?? '')     ||
        draft_enable     !== (obj.enable     !== false) ||
        draft_hide       !== !!obj.hide                 ||
        draft_priority   !== !!obj.priority             ||
        draft_sort       !== String(obj.sort ?? '')     ||
        draft_group      !== (obj.group      ?? '')     ||
        draft_notes      !== (obj.notes      ?? '')
    );
});

async function handle_save() {
    submit_status = 'processing';
    const api_cfg = untrack(() => $ae_api);

    const fields: Record<string, unknown> = {
        code:       draft_code.trim(),
        name:       draft_name.trim(),
        type:       draft_type,
        account_id: draft_account_id.trim() || null,
        enable:     draft_enable,
        hide:       draft_hide,
        priority:   draft_priority,
        sort:       draft_sort.trim()  || null,
        group:      draft_group.trim() || null,
        notes:      draft_notes.trim() || null
    };

    // for_type / for_id: only valid on create — backend stores for_id as an integer FK
    // and rejects the random string on PATCH
    if (is_new) {
        if (draft_for_type.trim()) fields.for_type = draft_for_type.trim();
        if (draft_for_id.trim())   fields.for_id   = draft_for_id.trim();
    }

    if (draft_type === 'json') {
        try { fields.json = JSON.parse(draft_value); }
        catch { fields.json = draft_value; }
    } else {
        fields.text = draft_value;
    }

    let result: Record<string, unknown> | null | false;
    if (is_new) {
        result = await api.create_ae_obj({ api_cfg, obj_type: 'data_store', fields });
    } else {
        result = await api.update_ae_obj({
            api_cfg,
            obj_type: 'data_store',
            obj_id: editing_obj!.id,
            fields
        });
    }

    if (result) {
        const ds_data = result as unknown as ae_DataStore;
        const ds_id = ds_data.data_store_id || ds_data.id;
        if (ds_id) {
            const text_val = ds_data.text ?? '';
            await db_core.data_store.put({
                ...ds_data,
                id:            ds_id,
                data_store_id: ds_id,
                updated_on:    ds_data.updated_on ?? new Date().toISOString(),
                text:          text_val,
                html:          text_val,
                json:          ds_data.json ?? null
            });
        }
        submit_status = 'saved';
        on_saved?.(result);
        setTimeout(() => { open = false; }, 600);
    } else {
        submit_status = 'error';
        setTimeout(() => { submit_status = 'idle'; }, 3000);
    }
}

async function handle_delete() {
    if (
        !editing_obj?.id ||
        !confirm(`Delete data store "${editing_obj.code}"?\n\nThis cannot be undone.`)
    ) return;
    const api_cfg = untrack(() => $ae_api);
    const result = await api.delete_ae_obj({
        api_cfg,
        obj_type: 'data_store',
        obj_id:   editing_obj.id,
        method:   'delete'
    });
    if (result) {
        await db_core.data_store.delete(editing_obj.id);
        on_deleted?.();
        open = false;
    }
}

function handle_cancel() {
    if (has_changes && !confirm('Discard unsaved changes?')) return;
    open = false;
}
</script>

<Modal
    title={is_new ? 'New Data Store' : `${draft_name || 'Unnamed'} — ${draft_code}`}
    bind:open
    autoclose={false}
    outsideclose={!has_changes}
    size="xl"
    class="w-full max-w-5xl">

    <form class="flex flex-col gap-4" onsubmit={(e) => { e.preventDefault(); handle_save(); }}>

        <!-- ── Code + Name ─────────────────────────────────────────────────────── -->
        <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
            <label class="label">
                <span class="text-xs font-bold opacity-70">Code <span class="text-error-500">*</span>
                    <span class="font-normal opacity-60">— unique key, snake_case convention</span>
                </span>
                <input
                    type="text"
                    class="input font-mono text-sm"
                    bind:value={draft_code}
                    required
                    readonly={readonly_code}
                    placeholder="event__my_event__my_store" />
            </label>
            <label class="label">
                <span class="text-xs font-bold opacity-70">Name <span class="text-error-500">*</span>
                    <span class="font-normal opacity-60">— human-readable label</span>
                </span>
                <input
                    type="text"
                    class="input"
                    bind:value={draft_name}
                    required
                    placeholder="My Data Store" />
            </label>
        </div>

        <!-- ── Type + Account ID ───────────────────────────────────────────────── -->
        <div class="grid grid-cols-1 gap-4 {show_account_field ? 'md:grid-cols-2' : ''}">
            <label class="label">
                <span class="text-xs font-bold opacity-70">Type
                    <span class="font-normal opacity-60">— determines how content is stored and rendered</span>
                </span>
                <select class="select" bind:value={draft_type}>
                    <option value="text">Text — plain text or string value</option>
                    <option value="html">HTML — rich markup (CodeMirror source / TipTap visual)</option>
                    <option value="json">JSON — structured data, parsed on save</option>
                    <option value="md">Markdown — rendered at display time</option>
                    <option value="sql">SQL — query string, stored as text</option>
                </select>
            </label>
            {#if show_account_field}
                <label class="label">
                    <span class="text-xs font-bold opacity-70">Account ID
                        <span class="font-normal opacity-60">— blank = global (shared across all accounts)</span>
                    </span>
                    <input
                        type="text"
                        class="input font-mono text-sm"
                        bind:value={draft_account_id}
                        placeholder="Leave blank for global" />
                </label>
            {/if}
        </div>

        <!-- ── For Type + For ID ───────────────────────────────────────────────── -->
        {#if show_for_fields}
            <div class="grid grid-cols-1 gap-4 md:grid-cols-2">
                <label class="label">
                    <span class="text-xs font-bold opacity-70">For Type
                        <span class="font-normal opacity-60">— polymorphic parent object type</span>
                    </span>
                    <input
                        type="text"
                        class="input font-mono text-sm"
                        bind:value={draft_for_type}
                        placeholder="event, event_session, person, journal_entry…" />
                </label>
                <div class="space-y-1">
                    <span class="text-xs font-bold opacity-70">For ID
                        <span class="font-normal opacity-60">— random string ID of the parent object</span>
                    </span>
                    {#if is_new}
                        <input
                            type="text"
                            class="input font-mono text-sm"
                            bind:value={draft_for_id}
                            placeholder="random string ID" />
                        <p class="text-[10px] opacity-50">
                            Backend resolves this string to an integer FK on create. Once set, For ID cannot be changed via the UI.
                        </p>
                    {:else}
                        <!-- for_id is stored as an integer FK; backend resolves string→int on create
                             but rejects the string on PATCH — display only after creation -->
                        <div class="input bg-surface-200-700-token flex items-center font-mono text-sm opacity-70">
                            {#if draft_for_id}
                                {draft_for_id}
                            {:else}
                                <span class="italic opacity-50">not set</span>
                            {/if}
                        </div>
                        <p class="text-[10px] opacity-50">
                            For ID is fixed at creation time. To change it, update directly in phpMyAdmin.
                        </p>
                    {/if}
                </div>
            </div>
        {/if}

        <!-- ── Content editor ─────────────────────────────────────────────────── -->
        <div class="space-y-2">
            <div class="flex items-center justify-between">
                <span class="text-xs font-bold opacity-70">
                    Content
                    {#if draft_type === 'json'}
                        <span class="font-normal opacity-60">— parsed as JSON on save; invalid JSON is stored as-is</span>
                    {:else if draft_type === 'sql'}
                        <span class="font-normal opacity-60">— stored as raw SQL string; not executed here</span>
                    {:else if draft_type === 'md'}
                        <span class="font-normal opacity-60">— Markdown; rendered by the display component</span>
                    {:else if draft_type === 'html'}
                        <span class="font-normal opacity-60">— HTML markup; rendered as raw HTML in display</span>
                    {/if}
                </span>
                {#if draft_type === 'html'}
                    <div class="flex items-center gap-1 rounded bg-surface-500/10 p-0.5">
                        <button
                            type="button"
                            class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
                            class:bg-primary-500={html_edit_mode === 'source'}
                            class:text-white={html_edit_mode === 'source'}
                            class:opacity-50={html_edit_mode !== 'source'}
                            onclick={() => (html_edit_mode = 'source')}
                            title="Edit raw HTML source">
                            <Code size="10" /> Source
                        </button>
                        <button
                            type="button"
                            class="flex items-center gap-1 rounded px-2 py-0.5 text-[10px] font-bold uppercase transition-all"
                            class:bg-primary-500={html_edit_mode === 'visual'}
                            class:text-white={html_edit_mode === 'visual'}
                            class:opacity-50={html_edit_mode !== 'visual'}
                            onclick={() => (html_edit_mode = 'visual')}
                            title="WYSIWYG visual editor">
                            <Eye size="10" /> Visual
                        </button>
                    </div>
                {/if}
            </div>

            {#if draft_type === 'html'}
                {#if html_edit_mode === 'source'}
                    <AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter HTML source…" />
                {:else}
                    <AE_Comp_Editor_TipTap bind:content={draft_value} placeholder="Enter HTML content…" />
                {/if}
            {:else if draft_type === 'json' || draft_type === 'sql' || draft_type === 'md'}
                <AE_Comp_Editor_CodeMirror bind:content={draft_value} placeholder="Enter {draft_type.toUpperCase()} content…" />
            {:else}
                <textarea
                    bind:value={draft_value}
                    class="textarea font-mono text-sm"
                    rows="15"
                    placeholder="Enter text content…"></textarea>
            {/if}
        </div>

        <!-- ── Advanced (collapsible) ──────────────────────────────────────────── -->
        <div class="overflow-hidden rounded-lg border border-surface-500/20">
            <button
                type="button"
                class="flex w-full items-center justify-between bg-surface-500/5 px-4 py-2 text-[10px] font-bold uppercase tracking-widest opacity-50 transition-opacity hover:opacity-80"
                onclick={() => (show_advanced = !show_advanced)}>
                <span>Advanced — Enable · Hide · Priority · Sort · Group · Notes</span>
                {#if show_advanced}<ChevronUp size={12} />{:else}<ChevronDown size={12} />{/if}
            </button>

            {#if show_advanced}
                <div class="space-y-4 p-4">
                    <div class="space-y-1">
                        <p class="text-[10px] font-bold uppercase tracking-widest opacity-40">Flags</p>
                        <div class="flex flex-wrap gap-x-6 gap-y-2">
                            <label class="flex cursor-pointer items-center gap-2 text-sm">
                                <input type="checkbox" class="checkbox" bind:checked={draft_enable} />
                                <span>Enable <span class="ml-1 text-[10px] font-normal opacity-50">— record is active and queryable</span></span>
                            </label>
                            <label class="flex cursor-pointer items-center gap-2 text-sm">
                                <input type="checkbox" class="checkbox" bind:checked={draft_hide} />
                                <span>Hide <span class="ml-1 text-[10px] font-normal opacity-50">— suppress from standard display</span></span>
                            </label>
                            <label class="flex cursor-pointer items-center gap-2 text-sm">
                                <input type="checkbox" class="checkbox" bind:checked={draft_priority} />
                                <span>Priority <span class="ml-1 text-[10px] font-normal opacity-50">— float above other records in sort</span></span>
                            </label>
                        </div>
                    </div>
                    <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
                        <label class="space-y-1">
                            <span class="text-xs font-bold opacity-70">Sort <span class="font-normal opacity-60">— numeric ordering; lower = higher position</span></span>
                            <input type="text" class="input input-sm font-mono text-xs" bind:value={draft_sort} placeholder="0" />
                        </label>
                        <label class="space-y-1">
                            <span class="text-xs font-bold opacity-70">Group <span class="font-normal opacity-60">— optional grouping or category label</span></span>
                            <input type="text" class="input input-sm font-mono text-xs" bind:value={draft_group} placeholder="group_name" />
                        </label>
                    </div>
                    <label class="space-y-1">
                        <span class="text-xs font-bold opacity-70">Notes <span class="font-normal opacity-60">— internal reference only; not shown to end users</span></span>
                        <input type="text" class="input text-sm" bind:value={draft_notes} placeholder="Internal notes or context…" />
                    </label>
                </div>
            {/if}
        </div>

        <!-- ── Footer actions ──────────────────────────────────────────────────── -->
        <div class="flex items-center justify-between border-t border-surface-500/20 pt-4">
            {#if !is_new}
                <button
                    type="button"
                    class="btn preset-tonal-error"
                    onclick={handle_delete}
                    title="Permanently delete this data store — cannot be undone">
                    <Trash2 size={14} class="mr-2" /> Delete
                </button>
            {:else}
                <div></div>
            {/if}

            <div class="flex gap-2">
                <button
                    type="button"
                    class="btn preset-tonal-surface"
                    onclick={handle_cancel}
                    title="Discard changes and close">
                    <X size={14} class="mr-2" /> Cancel
                </button>
                <button
                    type="submit"
                    class="btn preset-filled-primary-500"
                    disabled={!has_changes || submit_status === 'processing'}
                    title={is_new ? 'Create new data store' : 'Save changes'}>
                    {#if submit_status === 'processing'}
                        <LoaderCircle size={14} class="mr-2 animate-spin" />
                    {:else if submit_status === 'saved'}
                        <Check size={14} class="mr-2" />
                    {:else if submit_status === 'error'}
                        <X size={14} class="mr-2 text-error-500" />
                    {:else}
                        <Save size={14} class="mr-2" />
                    {/if}
                    {is_new ? 'Create' : 'Save'}
                </button>
            </div>
        </div>

    </form>
</Modal>
