<script>
  import { Plus, Trash2, Box, Search } from 'lucide-svelte';

  let { formData = $bindable(), tests = [], isDirty = $bindable(false), validationErrors = {} } = $props();

  let searchQuery = $state('');

  const members = $derived.by(() => {
    const testdefgrp = Array.isArray(formData?.testdefgrp) ? formData.testdefgrp : [];
    const detailMembers = Array.isArray(formData?.details?.members) ? formData.details.members : [];
    const sourceMembers = testdefgrp.length > 0 ? testdefgrp : detailMembers;

    return sourceMembers
      .map((m) => ({
        TestSiteID: m.TestSiteID,
        TestSiteCode: m.TestSiteCode,
        TestSiteName: m.TestSiteName,
        TestType: m.TestType,
        TestTypeLabel: m.TestTypeLabel,
        SeqScr: m.SeqScr || m.Member || 0,
      }))
      .sort((a, b) => parseInt(a.SeqScr || 0) - parseInt(b.SeqScr || 0));
  });

  const availableTests = $derived.by(() => {
    const query = searchQuery.trim().toLowerCase();
    if (!query) {
      return [];
    }

    const selectedIds = new Set(members.map((member) => Number(member.TestSiteID)));

    let filtered = tests.filter((test) =>
      Number(test.TestSiteID) !== Number(formData?.TestSiteID) &&
      !selectedIds.has(Number(test.TestSiteID)) &&
      test.IsActive !== '0' &&
      test.IsActive !== 0
    );

    filtered = filtered.filter((test) =>
      test.TestSiteCode?.toLowerCase().includes(query) ||
      test.TestSiteName?.toLowerCase().includes(query)
    );

    return filtered.sort((a, b) => parseInt(a.SeqScr || 0) - parseInt(b.SeqScr || 0));
  });

  const formulaSyntax = $derived.by(() => getFormulaSyntaxStatus(formData?.details?.FormulaCode || ''));

  const missingMemberCodes = $derived.by(() =>
    getMissingMemberCodes(formData?.details?.FormulaCode || '', members)
  );

  function handleFieldChange() {
    isDirty = true;
  }

  function tokenizeBraceRefs(formulaCode) {
    const matches = formulaCode.match(/\{[^{}]+\}/g) || [];
    return matches.map((token) => token.slice(1, -1));
  }

  function getFormulaSyntaxStatus(formulaCode) {
    const formula = formulaCode || '';
    if (!formula.trim()) {
      return { tone: 'neutral', text: 'Enter formula with test code references' };
    }

    const opens = (formula.match(/\{/g) || []).length;
    const closes = (formula.match(/\}/g) || []).length;
    if (opens !== closes) {
      return { tone: 'error', text: 'Unbalanced braces in formula' };
    }

    const stripped = formula.replace(/\{[^{}]+\}/g, '');
    if (stripped.includes('{') || stripped.includes('}')) {
      return { tone: 'error', text: 'Invalid brace token format detected' };
    }

    if (tokenizeBraceRefs(formula).length === 0) {
      return { tone: 'warning', text: 'No test references found' };
    }

    return { tone: 'success', text: 'Formula syntax looks good' };
  }

  function getMissingMemberCodes(formulaCode, selectedMembers) {
    const formula = formulaCode || '';
    return selectedMembers
      .map((member) => member.TestSiteCode)
      .filter((code) => code && !formula.includes(`{${code}}`));
  }

  function addMember(test) {
    const currentMembers = Array.isArray(formData?.testdefgrp)
      ? formData.testdefgrp
      : (Array.isArray(formData?.details?.members) ? formData.details.members : []);

    const newMember = {
      TestSiteID: test.TestSiteID,
      TestSiteCode: test.TestSiteCode || '',
      TestSiteName: test.TestSiteName || '',
      TestType: test.TestType || 'TEST',
      TestTypeLabel: test.TestTypeLabel || 'Test',
      SeqScr: test.SeqScr || '0',
    };

    if (Object.prototype.hasOwnProperty.call(formData, 'testdefgrp')) {
      formData.testdefgrp = [...currentMembers, newMember];
    } else {
      formData.details.members = [...currentMembers, newMember];
    }

    handleFieldChange();
  }

  function removeMember(testId) {
    const currentMembers = Array.isArray(formData?.testdefgrp)
      ? formData.testdefgrp
      : (Array.isArray(formData?.details?.members) ? formData.details.members : []);

    const remainingMembers = currentMembers.filter((member) => Number(member.TestSiteID) !== Number(testId));

    if (Object.prototype.hasOwnProperty.call(formData, 'testdefgrp')) {
      formData.testdefgrp = remainingMembers;
    } else {
      formData.details.members = remainingMembers;
    }

    handleFieldChange();
  }
</script>

<div class="space-y-4">
  <h2 class="text-lg font-semibold text-gray-800">Calculated Test Formula</h2>

  <div class="alert alert-info text-sm py-2">
    <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-4 w-4" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
    <div>
      <strong>Formula Syntax:</strong> Use curly braces to reference test codes, e.g., <code class="code">{'{HGB}'} + {'{MCV}'}</code>
    </div>
  </div>
  <p class="text-[10px] text-gray-500">Supported operators/functions are listed in <a href="/docs/calculator-operators.md" class="text-primary underline">docs/calculator-operators.md</a>.</p>

  <!-- Formula Code -->
  <div class="space-y-2">
    <div class="flex items-start justify-between gap-2">
      <label for="formulaCode" class="text-sm font-medium text-gray-700">
        Formula Code <span class="text-error">*</span>
      </label>
      <div class="text-xs">
        {#if formulaSyntax.tone === 'success'}
          <span class="badge badge-success badge-outline badge-sm">{formulaSyntax.text}</span>
        {:else if formulaSyntax.tone === 'warning'}
          <span class="badge badge-warning badge-outline badge-sm">{formulaSyntax.text}</span>
        {:else if formulaSyntax.tone === 'error'}
          <span class="badge badge-error badge-outline badge-sm">{formulaSyntax.text}</span>
        {:else}
          <span class="text-gray-500">{formulaSyntax.text}</span>
        {/if}
      </div>
    </div>
    <textarea
      id="formulaCode"
      class="textarea textarea-sm textarea-bordered w-full font-mono text-sm"
      bind:value={formData.details.FormulaCode}
      placeholder="e.g., {'{HGB}'} + {'{MCV}'} + {'{MCHC}'}"
      rows="2"
      oninput={handleFieldChange}
      required
    ></textarea>

    {#if validationErrors.FormulaCode}
      <p class="text-error text-xs">{validationErrors.FormulaCode}</p>
    {/if}

    {#if members.length > 0 && missingMemberCodes.length > 0}
      <p class="text-warning text-xs">
        Missing member references: {missingMemberCodes.map((code) => `{${code}}`).join(', ')}
      </p>
    {/if}
  </div>

  <div class="grid grid-cols-1 lg:grid-cols-2 gap-3 h-[380px] overflow-hidden">
    <div class="flex flex-col border border-base-300 rounded-lg bg-base-100 overflow-hidden">
      <div class="px-3 py-2 border-b border-base-300 bg-base-200 rounded-t-lg shrink-0">
        <h3 class="text-xs font-medium text-gray-700 mb-1">Available Tests</h3>
        <label class="input input-xs input-bordered flex items-center gap-2 w-full">
          <Search class="w-3.5 h-3.5 text-gray-400" />
          <input
            type="text"
            class="grow bg-transparent outline-none text-xs"
            placeholder="Search by code or name..."
            bind:value={searchQuery}
          />
        </label>
      </div>

      <div class="flex-1 overflow-y-auto p-1.5 space-y-0.5 min-h-0">
        {#if !searchQuery.trim()}
          <div class="text-center py-6 text-gray-500">
            <Box class="w-8 h-8 mx-auto mb-2 opacity-50" />
            <p class="text-xs">Start typing to search tests</p>
            <p class="text-[10px] opacity-70 mt-0.5">Search by test code or name</p>
          </div>
        {:else if availableTests.length === 0}
          <div class="text-center py-6 text-gray-500">
            <Box class="w-8 h-8 mx-auto mb-2 opacity-50" />
            <p class="text-xs">No tests found</p>
            <p class="text-[10px] opacity-70 mt-0.5">Try a different search term</p>
          </div>
        {:else}
          {#each availableTests as test (test.TestSiteID)}
            <div class="flex items-center justify-between px-1.5 py-1 hover:bg-base-200 rounded-md group">
              <div class="flex-1 min-w-0">
                <div class="flex items-center gap-1.5">
                  <span class="font-mono text-[10px] text-gray-500 w-6">{test.SeqScr || '-'}</span>
                  <span class="font-mono text-xs font-medium truncate">{test.TestSiteCode}</span>
                  <span class="badge badge-xs badge-ghost scale-90 origin-left">{test.TestType}</span>
                </div>
                <p class="text-[11px] text-gray-600 truncate pl-7">{test.TestSiteName}</p>
              </div>
              <button
                class="btn btn-ghost btn-xs opacity-0 group-hover:opacity-100 transition-opacity h-6 min-h-0 px-1"
                onclick={() => addMember(test)}
                title="Add to calculation"
              >
                <Plus class="w-3.5 h-3.5 text-primary" />
              </button>
            </div>
          {/each}
        {/if}
      </div>

      <div class="px-2 py-1 border-t border-base-300 text-[10px] text-gray-500 text-center shrink-0">
        {availableTests.length} tests available
      </div>
    </div>

    <div class="flex flex-col border border-base-300 rounded-lg bg-base-100 overflow-hidden">
      <div class="px-3 py-2 border-b border-base-300 bg-base-200 rounded-t-lg shrink-0">
        <div class="flex items-center justify-between gap-2">
          <h3 class="text-xs font-medium text-gray-700">Selected Members</h3>
          <span class="badge badge-xs badge-ghost">{members.length} selected</span>
        </div>
      </div>

      <div class="flex-1 overflow-y-auto min-h-0">
        {#if members.length === 0}
          <div class="flex flex-col items-center justify-center h-full text-gray-500 py-6">
            <Box class="w-8 h-8 mb-2 opacity-50" />
            <p class="text-xs font-medium">No members selected</p>
            <p class="text-[10px] opacity-70 mt-0.5">Search and add tests from the left panel</p>
          </div>
        {:else}
          <table class="table table-xs w-full">
            <thead class="sticky top-0 bg-base-200">
              <tr>
                <th class="w-10 text-center text-[10px] py-1">Seq</th>
                <th class="w-16 text-[10px] py-1">Code</th>
                <th class="text-[10px] py-1">Name</th>
                <th class="w-12 text-[10px] py-1">Type</th>
                <th class="w-8 text-center text-[10px] py-1"></th>
              </tr>
            </thead>
            <tbody>
              {#each members as member (member.TestSiteID)}
                <tr class="hover:bg-base-200">
                  <td class="text-center font-mono text-[10px] text-gray-600 py-1">{member.SeqScr}</td>
                  <td class="font-mono text-[10px] py-1">{member.TestSiteCode}</td>
                  <td class="text-[10px] truncate max-w-[120px] py-1" title={member.TestSiteName}>
                    {member.TestSiteName}
                  </td>
                  <td class="py-1">
                    <span class="badge badge-xs badge-ghost scale-90 origin-left">{member.TestType}</span>
                  </td>
                  <td class="text-center py-1">
                    <button
                      class="btn btn-ghost btn-xs text-error p-0 min-h-0 h-auto"
                      onclick={() => removeMember(member.TestSiteID)}
                      title="Remove"
                    >
                      <Trash2 class="w-3 h-3" />
                    </button>
                  </td>
                </tr>
              {/each}
            </tbody>
          </table>
        {/if}
      </div>

      <div class="px-2 py-1 border-t border-base-300 text-[10px] text-gray-500 shrink-0">
        <p>Use {'{CODE}'} tokens from these members in your formula.</p>
        {#if validationErrors.members}
          <p class="text-error mt-0.5">{validationErrors.members}</p>
        {/if}
      </div>
    </div>
  </div>
</div>
