<script lang="ts">
  import * as Select from "$lib/components/ui/select";
  import {
    state as _state,
    deleteBody,
    deleteHeader,
    insertHeader,
    selectEntry,
    sendRequest,
    setEntryAuth,
    updateBodyContent,
    updateEntryName,
    updateHeader,
    updateUrl,
  } from "$lib/state.svelte";
  import { Button } from "$lib/components/ui/button";
  import { Input } from "$lib/components/ui/input";
  import * as Tabs from "$lib/components/ui/tabs";
  import type { UrlError, WorkspaceEntry } from "$lib/types";
  import Editable from "./Editable.svelte";
  import Highlight, { LineNumbers } from "svelte-highlight";
  import json from "svelte-highlight/languages/json";
  import { atelierForest } from "svelte-highlight/styles";
  import { Loader, PlusIcon, Trash } from "@lucide/svelte";
  import CodeMirror from "./CodeMirror.svelte";
  import * as Resizable from "$lib/components/ui/resizable/index";
  import AuthParams from "./AuthParams.svelte";
  import Checkbox from "./ui/checkbox/checkbox.svelte";

  let requestPane: Resizable.Pane;
  let responsePane: Resizable.Pane;
  let isSending = $state(false);
  let response: any = $state();

  const parentAuth = $derived.by(() => {
    let parentId = _state.entry!!.parent_id;

    while (parentId != null) {
      const parent = _state.indexes[parentId];

      if (!parent) {
        console.warn("Parent index is null", parentId);
        return;
      }

      if (parent.auth_inherit) {
        parentId = parent.id;
        continue;
      }

      if (parent.auth == null) {
        return null;
      }

      return _state.auth.find((a) => a.id === parent.auth) ?? null;
    }
  });

  const referenceChain = $derived.by(() => {
    const parents = [];

    let parent = _state.entry!!.parent_id;

    while (parent != null) {
      parents.push(_state.indexes[parent]);
      parent = _state.indexes[parent].parent_id;
    }

    return parents.reverse();
  });

  async function handleSendRequest() {
    if (isSending) return;
    console.time("request");

    isSending = true;
    try {
      response = await sendRequest();
    } catch (e) {
      console.error("error sending request", e);
    } finally {
      if (responsePane.getSize() === 0) {
        requestPane.resize(50);
        responsePane.resize(50);
      }
      console.timeEnd("request");
      isSending = false;
    }
  }

  async function handleUrlUpdate(direct: boolean = false) {
    const u = direct ? _state.entry!!.url : reconstructUrl();

    try {
      await updateUrl(u, !direct);
    } catch (err) {
      console.error(err);
      const e = err as UrlError;
      switch (e.type) {
        case "Parse": {
          console.error("url parse error", e.error);
          break;
        }
        case "DuplicatePath": {
          console.error("url duplicate path error", e.error);
        }
        case "Db": {
          console.error("url persist error", e.error);
          break;
        }
      }
      return;
    }
  }

  /** Construct a URL from the binded input values for query and path parameters. */
  function reconstructUrl(): string {
    let url = _state.entry.workingUrl.pre;

    for (const param of _state.entry.workingUrl.path) {
      const [name, position] = param.value;

      if (param.type === "Static") {
        url += "/" + name;
        continue;
      }

      const replacement = _state.entry!!.path.find(
        (p) => p.position === position,
      );

      if (replacement !== undefined) {
        url += "/:" + replacement.name;
      } else {
        url += "/:" + name;
      }
    }

    if (_state.entry.workingUrl.query_params.length > 0) {
      url +=
        "?" +
        _state.entry
          .workingUrl!!.query_params.map((p) => `${p[0]}=${p[1]}`)
          .join("&");
    } else if (_state.entry.workingUrl!!.has_query) {
      url += "?";
    }

    return url;
  }
</script>

<svelte:head>
  {@html atelierForest}
</svelte:head>

{#snippet entryPath()}
  <!-- ENTRY PATH -->
  <div class="h-8 flex items-center">
    {#each referenceChain as ref}
      <Button
        class="p-0 h-fit cursor-pointer"
        onclick={() => selectEntry(ref.id)}
        variant="ghost"
      >
        {ref.name || ref.type + "(" + ref.id + ")"}
      </Button>
      <p class="pl-1 pr-1">/</p>
    {/each}
    <Editable
      bind:value={_state.entry!!.name}
      onSave={(value) => {
        updateEntryName(value);
      }}
    >
      {#snippet display({ value, startEdit })}
        <h1 ondblclick={startEdit}>
          {value || _state.entry!!.type + "(" + _state.entry!!.id + ")"}
        </h1>
      {/snippet}
    </Editable>
  </div>
{/snippet}

{#snippet authParams(
  entry: WorkspaceEntry & { auth: number | null; auth_inherit: boolean },
)}
  <div class="w-full p-4 pl-2">
    <Select.Root
      type="single"
      value={entry.auth_inherit ? "inherit" : (entry.auth?.toString() ?? "-")}
    >
      <Select.Trigger>
        {#if entry.auth != null && !entry.auth_inherit}
          {_state.auth.find((a) => a.id === entry.auth)?.name}
        {:else if entry.auth_inherit}
          Inherit ({parentAuth?.name})
        {:else}
          -
        {/if}
      </Select.Trigger>

      <Select.Content>
        <Select.Item onclick={() => setEntryAuth(null, false)} value="-">
          -
        </Select.Item>

        {#if entry.parent_id != null}
          <Select.Item onclick={() => setEntryAuth(null, true)} value="inherit">
            Inherit ({parentAuth?.name})
          </Select.Item>
        {/if}

        <Select.Separator />

        {#each _state.auth as auth}
          <Select.Item
            onclick={() => setEntryAuth(auth.id, false)}
            value={auth.id.toString()}
          >
            {auth.name}
          </Select.Item>
        {/each}
      </Select.Content>
    </Select.Root>
  </div>

  {#if entry.auth_inherit && parentAuth}
    <div class="opacity-75 p-4">
      <AuthParams auth={parentAuth} readonly={true} />
    </div>
  {:else if entry.auth}
    <div class="opacity-75 p-4">
      <AuthParams
        auth={_state.auth.find((a) => a.id === entry.auth)!!}
        readonly={true}
      />
    </div>
  {/if}
{/snippet}

{#if _state.entry?.type === "Collection"}
  <!-- COLLECTION VIEW -->

  {@render entryPath()}

  <div class="flex flex-wrap p-2 border">
    <h2 class="pb-2 w-full border-b">Auth</h2>
    {@render authParams(_state.entry!!)}
  </div>
{:else if _state.entry?.type === "Request"}
  <!-- REQUEST WORK AREA -->

  {@render entryPath()}

  <section class="h-[90%] space-y-4">
    <!-- URL BAR -->

    <div class="flex flex-wrap gap-3 mx-auto">
      <Input
        class="w-10/12 flex font-mono"
        bind:value={_state.entry.url}
        placeholder="https://api.example.com/resource"
        oninput={() => {
          handleUrlUpdate(true);
        }}
      />

      <Button
        class="w-1/12 flex items-center justify-center gap-2"
        disabled={isSending}
        onclick={handleSendRequest}
      >
        {#if isSending}
          <Loader class="h-4 w-4 animate-spin" />
          Sending
        {:else}
          Send
        {/if}
      </Button>

      <p class="w-full pl-1 text-xs text-muted-foreground">
        {_state.entry.expandedUrl ?? ""}
      </p>
    </div>

    <!-- ================= REQUEST PANEL ================= -->

    <Resizable.PaneGroup direction="vertical" class="flex-1 w-full rounded-lg">
      <Resizable.Pane defaultSize={100} bind:this={requestPane}>
        <Tabs.Root value="params" class="h-full flex flex-col">
          <Tabs.List class="shrink-0">
            <Tabs.Trigger value="params">Parameters</Tabs.Trigger>
            <Tabs.Trigger value="headers">Headers</Tabs.Trigger>
            <Tabs.Trigger value="body">Body</Tabs.Trigger>
            <Tabs.Trigger value="auth">Auth</Tabs.Trigger>
          </Tabs.List>

          <div class="flex-1 overflow-auto p-2">
            <!-- ================= PARAMETERS ================= -->

            <Tabs.Content value="params" class="space-y-4">
              {#if _state.entry.path.length > 0}
                <div>
                  <h3 class="mb-2 text-sm font-medium">Path</h3>
                  <div class="grid grid-cols-2 gap-2 text-sm">
                    {#each _state.entry.path as param}
                      <Input
                        bind:value={param.name}
                        placeholder="key"
                        oninput={() => handleUrlUpdate()}
                      />
                      <Input
                        bind:value={param.value}
                        placeholder="value"
                        oninput={() => handleUrlUpdate()}
                      />
                    {/each}
                  </div>
                </div>
              {/if}

              {#if _state.entry.workingUrl?.query_params.length > 0}
                <div>
                  <h3 class="mb-2 text-sm font-medium">Query</h3>
                  <div class="grid grid-cols-2 gap-2 text-sm">
                    {#each _state.entry.workingUrl.query_params as param}
                      <Input
                        bind:value={param[0]}
                        placeholder="key"
                        oninput={() => handleUrlUpdate()}
                      />
                      <Input
                        bind:value={param[1]}
                        placeholder="value"
                        oninput={() => handleUrlUpdate()}
                      />
                    {/each}
                  </div>
                </div>
              {/if}
            </Tabs.Content>

            <!-- ================= HEADERS ================= -->

            <Tabs.Content value="headers">
              <div
                class="w-10/12 mx-auto grid grid-cols-[auto_1fr] gap-2 items-center"
              >
                {#each _state.entry.headers as header}
                  <div class="contents">
                    <Input
                      bind:value={header.name}
                      placeholder="Name"
                      oninput={() =>
                        updateHeader(header.id, header.name, header.value)}
                    />

                    <Input
                      bind:value={header.value}
                      placeholder="Value"
                      oninput={() =>
                        updateHeader(header.id, header.name, header.value)}
                    />

                    <Trash
                      class="h-4 w-4 cursor-pointer text-muted-foreground hover:text-destructive"
                      onclick={() => deleteHeader(header.id)}
                    />
                  </div>
                {/each}

                <PlusIcon
                  class="border p-1 rounded-2xl mx-auto col-span-3 cursor-pointer"
                  onclick={() => insertHeader()}
                />
              </div>
            </Tabs.Content>

            <!-- ================= BODY ================= -->

            <Tabs.Content value="body" class="space-y-4">
              <Tabs.Root value={_state.entry.body === null ? "none" : "json"}>
                <Tabs.List>
                  <Tabs.Trigger value="none" onclick={deleteBody}
                    >None</Tabs.Trigger
                  >
                  <Tabs.Trigger value="json">JSON</Tabs.Trigger>
                  <Tabs.Trigger value="form">Form</Tabs.Trigger>
                  <Tabs.Trigger value="text">Text</Tabs.Trigger>
                </Tabs.List>

                <Tabs.Content value="json">
                  <CodeMirror
                    input={_state.entry.body?.body}
                    onStateChange={(update) => {
                      if (
                        update.docChanged &&
                        _state.entry.body?.body !== update.state.doc.toString()
                      ) {
                        updateBodyContent(update.state.doc.toString(), "Json");
                      }
                    }}
                  />
                </Tabs.Content>

                <Tabs.Content value="form">
                  <p class="text-sm text-muted-foreground">
                    Form body editor coming soon.
                  </p>
                </Tabs.Content>

                <Tabs.Content value="text">
                  <textarea
                    class="w-full min-h-[200px] rounded-md border bg-background p-2 font-mono text-sm"
                    placeholder="Raw text body"
                  ></textarea>
                </Tabs.Content>
              </Tabs.Root>
            </Tabs.Content>

            <!-- ================= AUTH ================= -->

            <Tabs.Content value="auth" class="space-y-4">
              {@render authParams(_state.entry!!)}
            </Tabs.Content>
          </div>
        </Tabs.Root>
      </Resizable.Pane>

      <Resizable.Handle
        withHandle
        class="p-1.5"
        ondblclick={() => {
          requestPane.resize(50);
          responsePane.resize(50);
        }}
      />

      <!-- RESPONSE -->

      <Resizable.Pane class="p-2" defaultSize={0} bind:this={responsePane}>
        {#if isSending}
          <div class="flex justify-center py-8">
            <Loader class="h-6 w-6 animate-spin text-muted-foreground" />
          </div>
        {:else if response}
          <!-- Prevents line number selection -->
          <p>{response.status}</p>
          <div
            class="
                  w-full
                  [&_td:first-child]:select-none
                  [&_td:first-child]:pointer-events-none
                "
          >
            <Highlight
              language={json}
              code={response.body.Json}
              let:highlighted
            >
              <LineNumbers {highlighted} wrapLines hideBorder />
            </Highlight>
          </div>
        {/if}
      </Resizable.Pane>
    </Resizable.PaneGroup>
  </section>
{/if}
