<script lang="ts">
  import { Clipboard } from "@lucide/svelte";
  import * as Select from "$lib/components/ui/select";
  import {
    state as _state,
    cancelRequest,
    deleteBody,
    deleteHeader,
    insertHeader,
    sendRequest,
    setEntryAuth,
    updateBodyContent,
    updateEntryName,
    updateHeader,
    updateQueryParamEnabled,
    updateUrl,
    type UrlUpdate,
  } 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 { HttpResponse, UrlError, WorkspaceEntry } from "$lib/types";
  import Editable from "./Editable.svelte";
  import { Loader, PlusIcon, Trash } from "@lucide/svelte";
  import BodyEditor from "./BodyEditor.svelte";
  import * as Resizable from "$lib/components/ui/resizable/index";
  import AuthParams from "./AuthParams.svelte";
  import Response from "./Response.svelte";
  import Checkbox from "./ui/checkbox/checkbox.svelte";

  let requestPane: Resizable.Pane;
  let responsePane: Resizable.Pane;

  let isSending = $derived(_state.pendingRequests.includes(_state.entry!!.id));

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

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

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

      if (entry.auth_inherit) {
        parentId = entry.parent_id;
        continue;
      }

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

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

  async function handleRequest() {
    if (isSending) {
      try {
        await cancelRequest();
      } catch (e) {
        console.error("error cancelling request", e);
      }
      return;
    }

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

  let updateUrlTimeout: number | undefined = $state();

  async function handleUrlUpdate(update: UrlUpdate) {
    if (updateUrlTimeout != undefined) {
      clearTimeout(updateUrlTimeout);
    }
    updateUrlTimeout = setTimeout(async () => {
      try {
        await updateUrl(update);
      } 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;
      }
    }, 200);
  }
</script>

{#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}

        {#if _state.auth.length > 0}
          <Select.Separator />

          {#each _state.auth as auth}
            <Select.Item
              onclick={() => setEntryAuth(auth.id, false)}
              value={auth.id.toString()}
            >
              {auth.name}
            </Select.Item>
          {/each}
        {/if}
      </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}

<!-- BEGIN COMPONENT -->

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

  <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 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 && _state.entry.type === "Request"}
  <!-- REQUEST WORK AREA -->

  <div class="flex w-full items-center">
    <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>

    {#if _state.entry.expandedUrl != null}
      <div class="flex justify-end items-center w-full pr-2">
        <p class="text-xs text-muted-foreground text-right">
          {_state.entry.expandedUrl ?? ""}
        </p>
        <Button
          onclick={() =>
            navigator.clipboard.writeText(_state.entry!!.expandedUrl)}
          variant="ghost"
          class="border-none mx-2 text-muted-foreground hover:text-foreground"
          size="icon-sm"><Clipboard /></Button
        >
      </div>
    {/if}
  </div>

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

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

      <Button class="flex items-center justify-center" onclick={handleRequest}>
        {#if isSending}
          <Loader class="h-4 w-4 animate-spin" />
          Cancel
        {:else}
          Send
        {/if}
      </Button>
    </div>

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

    <Resizable.PaneGroup direction="vertical" class="flex-1 w-full ">
      <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({
                            type: "Path",
                            url: _state.entry.url,
                            param,
                          })}
                      />
                      <Input
                        bind:value={param.value}
                        placeholder="value"
                        oninput={() =>
                          handleUrlUpdate({
                            type: "Path",
                            url: _state.entry.url,
                            param,
                          })}
                      />
                    {/each}
                  </div>
                </div>
              {/if}

              {#if _state.entry?.query?.length > 0}
                <div>
                  <h3 class="mb-2 text-sm font-medium">Query</h3>
                  <div class="grid grid-cols-3 gap-2 text-sm">
                    {#each _state.entry.query as param}
                      <Checkbox
                        checked={param.position != null}
                        onCheckedChange={() =>
                          updateQueryParamEnabled(param.id)}
                      />
                      <Input
                        bind:value={param.key}
                        placeholder="key"
                        oninput={() =>
                          handleUrlUpdate({
                            type: "Query",
                            url: _state.entry.url,
                            param,
                          })}
                      />
                      <Input
                        bind:value={param.value}
                        placeholder="value"
                        oninput={() =>
                          handleUrlUpdate({
                            type: "Query",
                            url: _state.entry.url,
                            param,
                          })}
                      />
                    {/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">
                  <BodyEditor
                    input={_state.entry.body?.content}
                    type={_state.entry.body?.ty}
                    onStateChange={(update) => {
                      if (
                        update.docChanged &&
                        _state.entry!!.body?.content !==
                          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
        class="hover:bg-primary"
        ondblclick={() => {
          requestPane.resize(50);
          responsePane.resize(50);
        }}
      />

      <!-- RESPONSE -->

      <Resizable.Pane
        class="flex flex-col"
        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 _state.responses[_state.entry!!.id]}
          <Response />
        {/if}
      </Resizable.Pane>
    </Resizable.PaneGroup>
  </section>
{/if}
