<template>
  <div class="p-6">
    <!-- Header -->
    <div class="flex items-center justify-between mb-6">
      <h1 class="text-[1.75rem] font-bold font-[family-name:var(--font-heading)] tracking-tight text-text-primary">Projects</h1>
      <button
        data-tour-id="new-project"
        @click="openCreateDialog"
        class="px-3 py-1.5 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150"
      >
        + Add
      </button>
    </div>

    <!-- Attention Needed -->
    <div v-if="attentionProjects.length > 0" role="region" aria-label="Projects needing attention" class="mb-6">
      <h2 class="text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-3">Attention Needed</h2>
      <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
        <div
          v-for="ap in attentionProjects"
          :key="ap.project.id"
          class="flex items-center gap-3 px-3 py-2 bg-bg-surface border border-border-subtle rounded-lg cursor-pointer hover:bg-bg-elevated transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
          @click="$router.push('/projects/' + ap.project.id)"
          tabindex="0"
          @keydown.enter="$router.push('/projects/' + ap.project.id)"
          role="button"
        >
          <div class="w-2 h-2 rounded-full shrink-0" :style="{ backgroundColor: ap.project.color }" aria-hidden="true" />
          <span class="text-[0.8125rem] text-text-primary truncate flex-1">{{ ap.project.name }}</span>
          <span class="text-[0.625rem] font-medium" :class="ap.health.color">{{ ap.health.status }}</span>
        </div>
      </div>
    </div>

    <!-- Archive filter + group toggle -->
    <div class="flex items-center justify-between mb-4">
      <div class="flex items-center gap-1" role="group" aria-label="Project filter">
        <button v-for="filter in (['active', 'archived', 'all'] as const)" :key="filter"
          @click="archiveFilter = filter"
          class="px-3 py-1.5 text-[0.6875rem] font-medium rounded-md transition-colors"
          :class="archiveFilter === filter ? 'bg-accent text-bg-base' : 'text-text-secondary hover:text-text-primary'"
          :aria-pressed="archiveFilter === filter"
        >
          {{ filter === 'active' ? 'Active' : filter === 'archived' ? 'Archived' : 'All' }}
        </button>
      </div>
      <button
        @click="toggleGroupByClient"
        class="flex items-center gap-1.5 px-3 py-1.5 text-[0.6875rem] font-medium rounded-md transition-colors"
        :class="groupByClient ? 'bg-accent text-bg-base' : 'text-text-secondary hover:text-text-primary'"
        :aria-pressed="groupByClient"
        aria-label="Group projects by client"
        v-tooltip="'Group by client'"
      >
        <Layers class="w-3.5 h-3.5" :stroke-width="1.5" aria-hidden="true" />
        Group
      </button>
    </div>

    <!-- Projects Grid (flat) -->
    <template v-if="!groupByClient">
      <TransitionGroup v-if="displayedProjects.length > 0" name="list" tag="div" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
        <div
          v-for="(project, index) in displayedProjects"
          :key="project.id"
          class="group bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] card-hover cursor-pointer"
          :class="project.archived ? 'opacity-60' : ''"
          :style="{ transitionDelay: `${index * 30}ms` }"
          tabindex="0"
          role="button"
          @click="$router.push('/projects/' + project.id)"
          @keydown.enter="$router.push('/projects/' + project.id)"
          @keydown.space.prevent="$router.push('/projects/' + project.id)"
        >
          <div class="flex border-l-[2px] hover:border-l-[3px] rounded-l-lg transition-all duration-150" :style="{ borderLeftColor: project.color }">
            <div class="flex-1 p-4">
              <div class="flex items-start justify-between">
                <div>
                  <h3 class="text-[0.8125rem] font-semibold text-text-primary">{{ project.name }}</h3>
                  <p class="text-xs text-text-secondary mt-0.5">{{ getClientName(project.client_id) }} · {{ project.budget_amount ? formatCurrency(project.budget_amount) + ' fixed' : formatCurrency(project.hourly_rate) + '/hr' }}</p>
                </div>
                <div class="flex items-center gap-1 ">
                  <button
                    @click.stop="openEditDialog(project)"
                    class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                    aria-label="Edit project"
                    v-tooltip="'Edit'"
                  >
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
                      <path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
                    </svg>
                  </button>
                  <button
                    @click.stop="toggleArchive(project)"
                    class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                    :aria-label="project.archived ? 'Unarchive project' : 'Archive project'"
                    v-tooltip="project.archived ? 'Unarchive' : 'Archive'"
                  >
                    <ArchiveRestore v-if="project.archived" class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                    <Archive v-else class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                  </button>
                  <button
                    @click.stop="confirmDelete(project)"
                    class="p-1.5 text-text-tertiary hover:text-status-error transition-colors duration-150"
                    aria-label="Delete project"
                    v-tooltip="'Delete'"
                  >
                    <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
                      <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                    </svg>
                  </button>
                </div>
              </div>
              <div v-if="project.budget_hours" class="mt-2">
                <div class="flex items-center justify-between mb-1">
                  <span class="text-[0.625rem] text-text-tertiary">{{ formatNumber(getBudgetUsed(project), 0) }}h / {{ project.budget_hours }}h</span>
                  <span class="text-[0.625rem]" :class="getBudgetPct(project) > 90 ? 'text-status-error-text' : getBudgetPct(project) > 75 ? 'text-status-warning' : 'text-text-tertiary'">{{ formatNumber(getBudgetPct(project), 0) }}%</span>
                </div>
                <div class="w-full bg-bg-elevated rounded-full h-1" role="progressbar" :aria-valuenow="Math.round(getBudgetPct(project))" aria-valuemin="0" aria-valuemax="100" :aria-label="'Budget progress: ' + formatNumber(getBudgetPct(project), 0) + '%'">
                  <div
                    class="h-1 rounded-full progress-bar"
                    :class="getBudgetPct(project) > 90 ? 'bg-status-error' : getBudgetPct(project) > 75 ? 'bg-status-warning' : 'bg-accent'"
                    :style="{ width: Math.min(getBudgetPct(project), 100) + '%' }"
                  />
                </div>
                <p v-if="getBudgetForecast(project)" class="text-[0.625rem] text-text-tertiary mt-1">
                  {{ getBudgetForecast(project) }}
                </p>
              </div>
              <div v-if="project.id && budgetStatus[project.id]" class="flex items-center gap-1 mt-1.5" role="status">
                <CheckCircle v-if="getProjectHealth(project, budgetStatus[project.id]).icon === 'success'"
                  class="w-3 h-3 text-status-running" aria-hidden="true" />
                <AlertTriangle v-else-if="getProjectHealth(project, budgetStatus[project.id]).icon === 'warning'"
                  class="w-3 h-3 text-status-warning" aria-hidden="true" />
                <XCircle v-else-if="getProjectHealth(project, budgetStatus[project.id]).icon === 'error'"
                  class="w-3 h-3 text-status-error" aria-hidden="true" />
                <span class="text-[0.625rem]" :class="getProjectHealth(project, budgetStatus[project.id]).color">
                  {{ getProjectHealth(project, budgetStatus[project.id]).status }}
                </span>
              </div>
              <!-- Estimates vs Actuals -->
              <div v-if="project.id && taskActuals[project.id]?.length" class="mt-3 pt-2 border-t border-border-subtle">
                <p class="text-[0.625rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Estimates vs Actuals</p>
                <div class="space-y-1.5">
                  <div v-for="ta in taskActuals[project.id]" :key="ta.task_id" class="flex items-center gap-2">
                    <span class="text-[0.6875rem] text-text-secondary truncate flex-1 min-w-0">{{ ta.task_name }}</span>
                    <span class="text-[0.625rem] font-mono text-text-tertiary shrink-0">{{ formatNumber(ta.actual_hours, 1) }}h</span>
                    <template v-if="ta.estimated_hours">
                      <span class="text-[0.625rem] text-text-tertiary shrink-0">/</span>
                      <span class="text-[0.625rem] font-mono text-text-tertiary shrink-0">{{ formatNumber(ta.estimated_hours, 1) }}h</span>
                      <div class="w-12 bg-bg-elevated rounded-full h-1 shrink-0" role="progressbar" :aria-valuenow="Math.round(ta.progress || 0)" aria-valuemin="0" aria-valuemax="100">
                        <div
                          class="h-1 rounded-full"
                          :class="(ta.progress || 0) > 100 ? 'bg-status-error' : 'bg-accent'"
                          :style="{ width: Math.min(ta.progress || 0, 100) + '%' }"
                        />
                      </div>
                    </template>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </TransitionGroup>

      <!-- Empty State -->
      <div v-if="displayedProjects.length === 0" class="flex flex-col items-center justify-center py-16">
        <FolderKanban class="w-12 h-12 text-text-tertiary animate-float" :stroke-width="1.5" aria-hidden="true" />
        <p class="text-sm text-text-secondary mt-4">No projects yet</p>
        <p class="text-xs text-text-tertiary mt-2 max-w-xs text-center">Projects organize your time entries and set billing rates for clients.</p>
        <button
          @click="openCreateDialog"
          class="mt-4 px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors"
        >
          Create Project
        </button>
      </div>
    </template>

    <!-- Projects Grid (grouped by client) -->
    <template v-else>
      <div v-if="groupedProjects.length > 0" class="space-y-6">
        <div v-for="group in groupedProjects" :key="group.client?.id ?? 'none'">
          <h2 class="text-[0.8125rem] font-medium mb-3" :class="group.client ? 'text-text-primary' : 'text-text-tertiary'">
            {{ group.client?.name || 'No Client' }}
            <span class="text-[0.6875rem] text-text-tertiary font-normal ml-1.5">{{ group.projects.length }}</span>
          </h2>
          <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
            <div
              v-for="project in group.projects"
              :key="project.id"
              class="group bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] card-hover cursor-pointer"
              :class="project.archived ? 'opacity-60' : ''"
              tabindex="0"
              role="button"
              @click="$router.push('/projects/' + project.id)"
              @keydown.enter="$router.push('/projects/' + project.id)"
              @keydown.space.prevent="$router.push('/projects/' + project.id)"
            >
              <div class="flex border-l-[2px] hover:border-l-[3px] rounded-l-lg transition-all duration-150" :style="{ borderLeftColor: project.color }">
                <div class="flex-1 p-4">
                  <div class="flex items-start justify-between">
                    <div>
                      <h3 class="text-[0.8125rem] font-semibold text-text-primary">{{ project.name }}</h3>
                      <p class="text-xs text-text-secondary mt-0.5">{{ getClientName(project.client_id) }} · {{ project.budget_amount ? formatCurrency(project.budget_amount) + ' fixed' : formatCurrency(project.hourly_rate) + '/hr' }}</p>
                    </div>
                    <div class="flex items-center gap-1 ">
                      <button
                        @click.stop="openEditDialog(project)"
                        class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                        aria-label="Edit project"
                        v-tooltip="'Edit'"
                      >
                        <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
                          <path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
                        </svg>
                      </button>
                      <button
                        @click.stop="toggleArchive(project)"
                        class="p-1.5 text-text-tertiary hover:text-text-secondary transition-colors duration-150"
                        :aria-label="project.archived ? 'Unarchive project' : 'Archive project'"
                        v-tooltip="project.archived ? 'Unarchive' : 'Archive'"
                      >
                        <ArchiveRestore v-if="project.archived" class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                        <Archive v-else class="h-3.5 w-3.5" :stroke-width="2" aria-hidden="true" />
                      </button>
                      <button
                        @click.stop="confirmDelete(project)"
                        class="p-1.5 text-text-tertiary hover:text-status-error transition-colors duration-150"
                        aria-label="Delete project"
                        v-tooltip="'Delete'"
                      >
                        <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
                          <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
                        </svg>
                      </button>
                    </div>
                  </div>
                  <div v-if="project.budget_hours" class="mt-2">
                    <div class="flex items-center justify-between mb-1">
                      <span class="text-[0.625rem] text-text-tertiary">{{ formatNumber(getBudgetUsed(project), 0) }}h / {{ project.budget_hours }}h</span>
                      <span class="text-[0.625rem]" :class="getBudgetPct(project) > 90 ? 'text-status-error-text' : getBudgetPct(project) > 75 ? 'text-status-warning' : 'text-text-tertiary'">{{ formatNumber(getBudgetPct(project), 0) }}%</span>
                    </div>
                    <div class="w-full bg-bg-elevated rounded-full h-1" role="progressbar" :aria-valuenow="Math.round(getBudgetPct(project))" aria-valuemin="0" aria-valuemax="100" :aria-label="'Budget progress: ' + formatNumber(getBudgetPct(project), 0) + '%'">
                      <div
                        class="h-1 rounded-full progress-bar"
                        :class="getBudgetPct(project) > 90 ? 'bg-status-error' : getBudgetPct(project) > 75 ? 'bg-status-warning' : 'bg-accent'"
                        :style="{ width: Math.min(getBudgetPct(project), 100) + '%' }"
                      />
                    </div>
                    <p v-if="getBudgetForecast(project)" class="text-[0.625rem] text-text-tertiary mt-1">
                      {{ getBudgetForecast(project) }}
                    </p>
                  </div>
                  <div v-if="project.id && budgetStatus[project.id]" class="flex items-center gap-1 mt-1.5" role="status">
                    <CheckCircle v-if="getProjectHealth(project, budgetStatus[project.id]).icon === 'success'"
                      class="w-3 h-3 text-status-running" aria-hidden="true" />
                    <AlertTriangle v-else-if="getProjectHealth(project, budgetStatus[project.id]).icon === 'warning'"
                      class="w-3 h-3 text-status-warning" aria-hidden="true" />
                    <XCircle v-else-if="getProjectHealth(project, budgetStatus[project.id]).icon === 'error'"
                      class="w-3 h-3 text-status-error" aria-hidden="true" />
                    <span class="text-[0.625rem]" :class="getProjectHealth(project, budgetStatus[project.id]).color">
                      {{ getProjectHealth(project, budgetStatus[project.id]).status }}
                    </span>
                  </div>
                  <!-- Estimates vs Actuals -->
                  <div v-if="project.id && taskActuals[project.id]?.length" class="mt-3 pt-2 border-t border-border-subtle">
                    <p class="text-[0.625rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Estimates vs Actuals</p>
                    <div class="space-y-1.5">
                      <div v-for="ta in taskActuals[project.id]" :key="ta.task_id" class="flex items-center gap-2">
                        <span class="text-[0.6875rem] text-text-secondary truncate flex-1 min-w-0">{{ ta.task_name }}</span>
                        <span class="text-[0.625rem] font-mono text-text-tertiary shrink-0">{{ formatNumber(ta.actual_hours, 1) }}h</span>
                        <template v-if="ta.estimated_hours">
                          <span class="text-[0.625rem] text-text-tertiary shrink-0">/</span>
                          <span class="text-[0.625rem] font-mono text-text-tertiary shrink-0">{{ formatNumber(ta.estimated_hours, 1) }}h</span>
                          <div class="w-12 bg-bg-elevated rounded-full h-1 shrink-0" role="progressbar" :aria-valuenow="Math.round(ta.progress || 0)" aria-valuemin="0" aria-valuemax="100">
                            <div
                              class="h-1 rounded-full"
                              :class="(ta.progress || 0) > 100 ? 'bg-status-error' : 'bg-accent'"
                              :style="{ width: Math.min(ta.progress || 0, 100) + '%' }"
                            />
                          </div>
                        </template>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- Empty State (grouped) -->
      <div v-else class="flex flex-col items-center justify-center py-16">
        <FolderKanban class="w-12 h-12 text-text-tertiary animate-float" :stroke-width="1.5" aria-hidden="true" />
        <p class="text-sm text-text-secondary mt-4">No projects yet</p>
        <p class="text-xs text-text-tertiary mt-2 max-w-xs text-center">Projects organize your time entries and set billing rates for clients.</p>
        <button
          @click="openCreateDialog"
          class="mt-4 px-4 py-2 bg-accent text-bg-base text-xs font-medium rounded-lg hover:bg-accent-hover transition-colors"
        >
          Create Project
        </button>
      </div>
    </template>

    <!-- Create/Edit Dialog -->
    <Transition name="modal">
    <div
      v-if="showDialog"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="tryCloseDialog"
    >
      <div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-5xl p-6 max-h-[calc(100vh-2rem)] overflow-y-auto" role="dialog" aria-modal="true" aria-labelledby="project-dialog-title">
        <h2 id="project-dialog-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-4">
          {{ editingProject ? 'Edit Project' : 'Create Project' }}
        </h2>

        <form @submit.prevent="handleSubmit" class="space-y-4">
          <!-- Three-column layout: Identity | Billing | Settings -->
          <div class="grid grid-cols-3 gap-6">
            <!-- Column 1: Identity -->
            <div class="space-y-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Name *</label>
                <input
                  v-model="formData.name"
                  type="text"
                  required
                  class="w-full px-3 py-2 bg-bg-inset border border-border-subtle rounded-lg text-[0.8125rem] text-text-primary focus:outline-none focus:border-border-visible"
                  placeholder="Project name"
                />
              </div>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Client</label>
                <AppSelect
                  v-model="formData.client_id"
                  :options="[...clientsStore.clients, { id: -1, name: '+ New Client' }]"
                  label-key="name"
                  value-key="id"
                  placeholder="No client"
                  :placeholder-value="undefined"
                />
                <div v-if="formData.client_id === -1" class="mt-2 p-3 bg-bg-elevated rounded-lg space-y-2">
                  <input v-model="newClientName" placeholder="Client name" class="w-full bg-bg-base border border-border-subtle rounded-lg px-3 py-2 text-[0.8125rem] text-text-primary placeholder-text-tertiary outline-none focus:border-accent transition-colors" />
                  <input v-model="newClientEmail" placeholder="Email (optional)" class="w-full bg-bg-base border border-border-subtle rounded-lg px-3 py-2 text-[0.8125rem] text-text-primary placeholder-text-tertiary outline-none focus:border-accent transition-colors" />
                  <div class="flex gap-2">
                    <button @click="saveInlineClient" class="px-3 py-1.5 text-[0.6875rem] bg-accent text-bg-base font-medium rounded-md hover:bg-accent-hover transition-colors">Save Client</button>
                    <button @click="cancelInlineClient" class="px-3 py-1.5 text-[0.6875rem] text-text-tertiary hover:text-text-secondary transition-colors">Cancel</button>
                  </div>
                </div>
              </div>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Color</label>
                <AppColorPicker
                  v-model="formData.color"
                  :presets="colorPresets"
                />
              </div>
            </div>

            <!-- Column 2: Billing -->
            <div class="space-y-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Billing Type</label>
                <div class="flex rounded-lg border border-border-subtle overflow-hidden">
                  <button
                    type="button"
                    @click="billingType = 'hourly'"
                    class="flex-1 px-3 py-1.5 text-[0.75rem] font-medium transition-colors duration-150"
                    :class="billingType === 'hourly' ? 'bg-accent text-bg-base' : 'text-text-secondary hover:bg-bg-elevated'"
                  >
                    Hourly
                  </button>
                  <button
                    type="button"
                    @click="billingType = 'fixed'"
                    class="flex-1 px-3 py-1.5 text-[0.75rem] font-medium transition-colors duration-150 border-l border-border-subtle"
                    :class="billingType === 'fixed' ? 'bg-accent text-bg-base' : 'text-text-secondary hover:bg-bg-elevated'"
                  >
                    Fixed Budget
                  </button>
                </div>
              </div>

              <!-- Hourly billing fields -->
              <template v-if="billingType === 'hourly'">
                <div>
                  <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Hourly Rate ({{ getCurrencySymbol() }})</label>
                  <AppNumberInput
                    v-model="formData.hourly_rate"
                    :min="0"
                    :step="1"
                    :precision="2"
                    :prefix="getCurrencySymbol()"
                  />
                </div>
                <div>
                  <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Budget Hours</label>
                  <AppNumberInput
                    :model-value="formData.budget_hours ?? 0"
                    @update:model-value="formData.budget_hours = $event || null"
                    :min="0"
                    :step="1"
                    :precision="0"
                    placeholder="No limit"
                  />
                </div>
              </template>

              <!-- Fixed budget fields -->
              <template v-if="billingType === 'fixed'">
                <div>
                  <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Budget Amount ({{ getCurrencySymbol() }})</label>
                  <AppNumberInput
                    :model-value="formData.budget_amount ?? 0"
                    @update:model-value="formData.budget_amount = $event || null"
                    :min="0"
                    :step="100"
                    :precision="2"
                    :prefix="getCurrencySymbol()"
                  />
                </div>
                <div>
                  <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Budget Hours</label>
                  <AppNumberInput
                    :model-value="formData.budget_hours ?? 0"
                    @update:model-value="formData.budget_hours = $event || null"
                    :min="0"
                    :step="1"
                    :precision="0"
                    placeholder="No limit"
                  />
                </div>
              </template>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Currency</label>
                <AppSelect
                  v-model="formData.currency"
                  :options="currencyOptions"
                  label-key="label"
                  value-key="value"
                  placeholder="Default (inherit)"
                  :placeholder-value="null"
                />
              </div>
            </div>

            <!-- Column 3: Settings -->
            <div class="space-y-4">
              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Timeline Recording</label>
                <AppSelect
                  v-model="formData.timeline_override"
                  :options="timelineOptions"
                  label-key="label"
                  value-key="value"
                  placeholder="Inherit global"
                  :placeholder-value="null"
                />
              </div>

              <div>
                <label class="block text-[0.6875rem] text-text-tertiary uppercase tracking-[0.08em] mb-1.5">Notes</label>
                <textarea v-model="formData.notes" rows="6" class="w-full bg-bg-base border border-border-subtle rounded-lg px-3 py-2 text-[0.8125rem] text-text-primary placeholder-text-tertiary outline-none focus:border-accent transition-colors resize-y" placeholder="Project notes, scope, contacts..." />
              </div>
            </div>
          </div>

          <!-- Tasks + Tracked Apps side by side -->
          <div class="grid grid-cols-2 gap-4">
            <!-- Tasks -->
            <div class="border border-border-subtle rounded-lg overflow-hidden">
              <button
                type="button"
                @click="tasksExpanded = !tasksExpanded"
                class="w-full flex items-center justify-between px-3 py-2.5 text-[0.8125rem] text-text-primary hover:bg-bg-elevated transition-colors"
              >
                <span>Tasks ({{ allTasks.length }})</span>
                <ChevronDown
                  class="w-4 h-4 text-text-tertiary transition-transform duration-200"
                  :class="{ 'rotate-180': tasksExpanded }"
                  :stroke-width="1.5"
                  aria-hidden="true"
                />
              </button>
              <div v-if="tasksExpanded" class="border-t border-border-subtle px-3 py-3 space-y-2.5">
                <div v-if="allTasks.length > 0" class="space-y-1.5 max-h-40 overflow-y-auto">
                  <div
                    v-for="(task, i) in allTasks"
                    :key="task.id ?? `pending-${i}`"
                    class="flex items-center justify-between gap-2 px-2.5 py-1.5 bg-bg-inset rounded-lg"
                  >
                    <!-- Edit mode -->
                    <template v-if="task.id && editingTaskId === task.id">
                      <input
                        v-model="editingTaskName"
                        type="text"
                        aria-label="Task name"
                        autofocus
                        class="flex-1 px-2 py-1 bg-bg-base border border-border-subtle rounded text-[0.75rem] text-text-primary focus:outline-none focus:border-border-visible"
                        @keydown.enter.prevent="saveEditTask(task)"
                        @keydown.escape.prevent="cancelEditTask"
                      />
                      <AppNumberInput
                        :model-value="editingTaskHours ?? 0"
                        @update:model-value="editingTaskHours = $event || null"
                        :min="0"
                        :step="0.5"
                        :precision="1"
                        placeholder="Est. hrs"
                        class="w-24"
                      />
                      <button
                        type="button"
                        @click="saveEditTask(task)"
                        class="px-2 py-1 bg-accent text-bg-base text-[0.6875rem] font-medium rounded hover:bg-accent-hover transition-colors"
                      >
                        Save
                      </button>
                      <button
                        type="button"
                        @click="cancelEditTask"
                        class="px-2 py-1 text-text-secondary text-[0.6875rem] hover:text-text-primary transition-colors"
                      >
                        Cancel
                      </button>
                    </template>
                    <!-- Display mode -->
                    <template v-else>
                      <span class="text-[0.75rem] text-text-primary truncate flex-1">{{ task.name }}</span>
                      <span v-if="task.estimated_hours" class="text-[0.625rem] text-text-tertiary bg-bg-elevated px-1.5 py-0.5 rounded-full shrink-0">
                        {{ task.estimated_hours }}h
                      </span>
                      <button
                        v-if="task.id"
                        type="button"
                        @click="startEditTask(task)"
                        class="p-1 text-text-tertiary hover:text-text-secondary transition-colors shrink-0"
                        aria-label="Rename task"
                      >
                        <Pencil class="w-3.5 h-3.5" :stroke-width="1.5" aria-hidden="true" />
                      </button>
                      <button
                        type="button"
                        @click="removeTask(task, i)"
                        class="p-1 text-text-tertiary hover:text-status-error transition-colors shrink-0"
                        aria-label="Remove task"
                      >
                        <X class="w-3.5 h-3.5" :stroke-width="1.5" aria-hidden="true" />
                      </button>
                    </template>
                  </div>
                </div>
                <p v-else class="text-[0.6875rem] text-text-tertiary">No tasks. Add tasks to break down project work.</p>
                <div class="flex items-center gap-2 pt-1">
                  <input
                    v-model="newTaskName"
                    type="text"
                    class="flex-1 px-2.5 py-1.5 bg-bg-inset border border-border-subtle rounded-lg text-[0.75rem] text-text-primary placeholder-text-tertiary focus:outline-none focus:border-border-visible"
                    placeholder="New task name..."
                    @keydown.enter.prevent="addTask"
                  />
                  <button
                    type="button"
                    @click="addTask"
                    :disabled="!newTaskName.trim()"
                    class="px-3 py-1.5 bg-accent text-bg-base text-[0.75rem] font-medium rounded-lg hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
                  >
                    Add
                  </button>
                </div>
              </div>
            </div>

            <!-- Tracked Apps -->
            <div class="border border-border-subtle rounded-lg overflow-hidden">
              <button
                type="button"
                @click="trackedAppsExpanded = !trackedAppsExpanded"
                class="w-full flex items-center justify-between px-3 py-2.5 text-[0.8125rem] text-text-primary hover:bg-bg-elevated transition-colors"
              >
                <span>Tracked Apps ({{ allTrackedApps.length }})</span>
                <ChevronDown
                  class="w-4 h-4 text-text-tertiary transition-transform duration-200"
                  :class="{ 'rotate-180': trackedAppsExpanded }"
                  :stroke-width="1.5"
                  aria-hidden="true"
                />
              </button>
              <div v-if="trackedAppsExpanded" class="border-t border-border-subtle px-3 py-3 space-y-2.5">
                <div v-if="allTrackedApps.length > 0" class="space-y-1.5 max-h-40 overflow-y-auto">
                  <div
                    v-for="app in allTrackedApps"
                    :key="app.exe_path || app.exe_name"
                    class="flex items-center justify-between gap-2 px-2.5 py-1.5 bg-bg-inset rounded-lg"
                  >
                    <div class="flex items-center gap-2 flex-1 min-w-0">
                      <img
                        v-if="app.icon"
                        :src="app.icon"
                        class="w-4 h-4 shrink-0"
                        alt=""
                      />
                      <div v-else class="w-4 h-4 shrink-0 rounded bg-bg-elevated" />
                      <div class="flex-1 min-w-0">
                        <p class="text-[0.75rem] text-text-primary truncate">{{ app.display_name || app.exe_name }}</p>
                        <p class="text-[0.6875rem] text-text-tertiary truncate">{{ app.exe_name }}</p>
                      </div>
                    </div>
                    <button
                      type="button"
                      @click="removeTrackedApp(app)"
                      class="p-1 text-text-tertiary hover:text-status-error transition-colors shrink-0"
                      aria-label="Remove tracked app"
                    >
                      <X class="w-3.5 h-3.5" :stroke-width="1.5" aria-hidden="true" />
                    </button>
                  </div>
                </div>
                <p v-else class="text-[0.6875rem] text-text-tertiary">No tracked apps. Timer will run without app visibility checks.</p>
                <div class="flex items-center gap-2 pt-1">
                  <RunningAppsPicker :exclude-paths="excludedPaths" @select="addTrackedAppFromPicker" />
                  <button
                    type="button"
                    @click="browseForApp"
                    class="flex items-center gap-1.5 px-2.5 py-1.5 border border-border-subtle text-text-secondary text-[0.75rem] rounded-lg hover:bg-bg-elevated hover:text-text-primary transition-colors"
                  >
                    <FolderOpen class="w-3.5 h-3.5" :stroke-width="1.5" aria-hidden="true" />
                    Browse...
                  </button>
                </div>
              </div>
            </div>
          </div>

          <!-- Buttons -->
          <div class="flex justify-end gap-3 pt-2">
            <button
              type="button"
              @click="closeDialog"
              class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
            >
              Cancel
            </button>
            <button
              type="submit"
              class="px-4 py-2 bg-accent text-bg-base font-medium rounded-lg hover:bg-accent-hover transition-colors duration-150"
            >
              {{ editingProject ? 'Update' : 'Create' }}
            </button>
          </div>
        </form>
      </div>
    </div>
    </Transition>

    <!-- Delete Confirmation Dialog -->
    <Transition name="modal">
    <div
      v-if="showDeleteDialog"
      class="fixed inset-0 bg-black/70 backdrop-blur-[4px] flex items-center justify-center p-4 z-50"
      @click.self="cancelDelete"
    >
      <div class="bg-bg-surface border border-border-subtle rounded-lg shadow-[0_1px_3px_rgba(0,0,0,0.3)] w-full max-w-sm p-6" role="alertdialog" aria-modal="true" aria-labelledby="delete-project-title" aria-describedby="delete-project-desc">
        <h2 id="delete-project-title" class="text-[1.125rem] font-semibold font-[family-name:var(--font-heading)] text-text-primary mb-2">Delete Project</h2>
        <p id="delete-project-desc" class="text-[0.75rem] text-text-secondary mb-6">
          Are you sure you want to delete "{{ projectToDelete?.name }}"? This action cannot be undone.
        </p>
        <div class="flex justify-end gap-3">
          <button
            @click="cancelDelete"
            class="px-4 py-2 border border-border-subtle text-text-secondary rounded-lg hover:bg-bg-elevated transition-colors duration-150"
          >
            Cancel
          </button>
          <button
            @click="handleDelete"
            class="px-4 py-2 border border-status-error text-status-error-text font-medium rounded-lg hover:bg-status-error/10 transition-colors duration-150"
          >
            Delete
          </button>
        </div>
      </div>
    </div>
    </Transition>

    <AppCascadeDeleteDialog
      :show="showCascadeDelete"
      entity-type="project"
      :entity-name="projectToDelete?.name || ''"
      :impacts="cascadeImpacts"
      @confirm="handleCascadeConfirm"
      @cancel="showCascadeDelete = false"
    />

    <AppDiscardDialog :show="showDiscardDialog" @cancel="cancelDiscard" @discard="confirmDiscard" />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { FolderKanban, ChevronDown, X, FolderOpen, Pencil, CheckCircle, AlertTriangle, XCircle, Archive, ArchiveRestore, Layers } from 'lucide-vue-next'
import { invoke } from '@tauri-apps/api/core'
import { open as openFileDialog } from '@tauri-apps/plugin-dialog'
import AppNumberInput from '../components/AppNumberInput.vue'
import AppSelect from '../components/AppSelect.vue'
import AppDiscardDialog from '../components/AppDiscardDialog.vue'
import AppCascadeDeleteDialog from '../components/AppCascadeDeleteDialog.vue'
import RunningAppsPicker from '../components/RunningAppsPicker.vue'
import AppColorPicker from '../components/AppColorPicker.vue'
import { useProjectsStore, type Project, type Task } from '../stores/projects'
import { useClientsStore } from '../stores/clients'
import { useSettingsStore } from '../stores/settings'
import { useToastStore } from '../stores/toast'
import { formatCurrency, formatNumber, getCurrencySymbol, getCurrencies } from '../utils/locale'
import { useFormGuard } from '../utils/formGuard'

interface TrackedApp {
  id?: number
  project_id: number
  exe_name: string
  exe_path?: string
  display_name?: string
  icon?: string
}


const colorPresets = ['#D97706', '#3B82F6', '#8B5CF6', '#EC4899', '#10B981', '#EF4444', '#06B6D4', '#6B7280']

const currencyOptions = computed(() =>
  getCurrencies().map(c => ({ label: `${c.code} - ${c.name}`, value: c.code }))
)

const timelineOptions = [
  { label: 'On', value: 'on' },
  { label: 'Off', value: 'off' },
]

const projectsStore = useProjectsStore()
const clientsStore = useClientsStore()
const settingsStore = useSettingsStore()
const route = useRoute()
const router = useRouter()

// Archive filter
const archiveFilter = ref<'active' | 'archived' | 'all'>('active')
const displayedProjects = computed(() => {
  if (archiveFilter.value === 'active') return projectsStore.activeProjects
  if (archiveFilter.value === 'archived') return projectsStore.archivedProjects
  return projectsStore.projects
})

// Group by client
const groupByClient = ref(settingsStore.settings.projects_group_by_client === 'true')

interface ProjectGroup {
  client: { id: number; name: string } | null
  projects: Project[]
}

const groupedProjects = computed<ProjectGroup[]>(() => {
  const projects = displayedProjects.value
  const groups = new Map<number | null, Project[]>()

  for (const p of projects) {
    const key = p.client_id ?? null
    if (!groups.has(key)) groups.set(key, [])
    groups.get(key)!.push(p)
  }

  const result: ProjectGroup[] = []
  const noClientGroup = groups.get(null)
  groups.delete(null)

  const sorted = [...groups.entries()].sort((a, b) => {
    const nameA = clientsStore.clients.find(c => c.id === a[0])?.name || ''
    const nameB = clientsStore.clients.find(c => c.id === b[0])?.name || ''
    return nameA.localeCompare(nameB)
  })

  for (const [clientId, clientProjects] of sorted) {
    const client = clientsStore.clients.find(c => c.id === clientId)
    result.push({
      client: client ? { id: client.id!, name: client.name } : null,
      projects: clientProjects,
    })
  }

  if (noClientGroup && noClientGroup.length > 0) {
    result.push({ client: null, projects: noClientGroup })
  }

  return result
})

function toggleGroupByClient() {
  groupByClient.value = !groupByClient.value
  settingsStore.updateSetting('projects_group_by_client', groupByClient.value ? 'true' : 'false')
}

async function toggleArchive(project: Project) {
  await projectsStore.updateProject({ ...project, archived: !project.archived })
}

// Inline client creation
const newClientName = ref('')
const newClientEmail = ref('')

async function saveInlineClient() {
  if (!newClientName.value.trim()) return
  const id = await clientsStore.createClient({
    name: newClientName.value.trim(),
    email: newClientEmail.value.trim() || undefined,
  } as any)
  if (id) {
    formData.client_id = id
    newClientName.value = ''
    newClientEmail.value = ''
  }
}

function cancelInlineClient() {
  formData.client_id = undefined
  newClientName.value = ''
  newClientEmail.value = ''
}

const { showDiscardDialog, snapshot: snapshotForm, tryClose: tryCloseForm, confirmDiscard, cancelDiscard } = useFormGuard()

function getFormData() {
  return { name: formData.name, client_id: formData.client_id, hourly_rate: formData.hourly_rate, color: formData.color, archived: formData.archived, budget_hours: formData.budget_hours, budget_amount: formData.budget_amount, timeline_override: formData.timeline_override, notes: formData.notes, currency: formData.currency }
}

function tryCloseDialog() {
  tryCloseForm(getFormData(), closeDialog)
}

// Dialog state
const showDialog = ref(false)
const showDeleteDialog = ref(false)
const showCascadeDelete = ref(false)
const cascadeImpacts = ref<{ label: string; count: number }[]>([])
const editingProject = ref<Project | null>(null)
const projectToDelete = ref<Project | null>(null)

// Billing type
const billingType = ref<'hourly' | 'fixed'>('hourly')

// Task state
const projectTasksList = ref<Task[]>([])
const pendingNewTasks = ref<string[]>([])
const pendingRemoveTaskIds = ref<number[]>([])
const newTaskName = ref('')
const tasksExpanded = ref(false)
const editingTaskId = ref<number | null>(null)
const editingTaskName = ref('')
const editingTaskHours = ref<number | null>(null)

const allTasks = computed(() => {
  const existing = projectTasksList.value.filter(t => !pendingRemoveTaskIds.value.includes(t.id!))
  const pending = pendingNewTasks.value.map(name => ({ name, project_id: 0 } as Task))
  return [...existing, ...pending]
})

async function loadProjectTasks(projectId: number) {
  projectTasksList.value = await projectsStore.fetchTasks(projectId)
}

function addTask() {
  const name = newTaskName.value.trim()
  if (!name) return
  pendingNewTasks.value.push(name)
  newTaskName.value = ''
}

function removeTask(task: Task, index: number) {
  if (task.id) {
    pendingRemoveTaskIds.value.push(task.id)
  } else {
    // It's a pending task - index offset by existing tasks count
    const existingCount = projectTasksList.value.filter(t => !pendingRemoveTaskIds.value.includes(t.id!)).length
    pendingNewTasks.value.splice(index - existingCount, 1)
  }
}

function startEditTask(task: Task) {
  editingTaskId.value = task.id || null
  editingTaskName.value = task.name
  editingTaskHours.value = task.estimated_hours ?? null
}

function cancelEditTask() {
  editingTaskId.value = null
  editingTaskName.value = ''
  editingTaskHours.value = null
}

async function saveEditTask(task: Task) {
  if (!task.id || !editingTaskName.value.trim()) return
  const updated: Task = {
    ...task,
    name: editingTaskName.value.trim(),
    estimated_hours: editingTaskHours.value,
  }
  const ok = await projectsStore.updateTask(updated)
  if (ok) {
    const idx = projectTasksList.value.findIndex(t => t.id === task.id)
    if (idx !== -1) projectTasksList.value[idx] = updated
    cancelEditTask()
  }
}

async function saveTasks(projectId: number) {
  for (const id of pendingRemoveTaskIds.value) {
    await projectsStore.deleteTask(id)
  }
  for (const name of pendingNewTasks.value) {
    await projectsStore.createTask({ project_id: projectId, name })
  }
  pendingNewTasks.value = []
  pendingRemoveTaskIds.value = []
}

// Tracked apps state
const trackedApps = ref<TrackedApp[]>([])
const pendingAddApps = ref<TrackedApp[]>([])
const pendingRemoveIds = ref<number[]>([])
const trackedAppsExpanded = ref(false)

const allTrackedApps = computed(() => {
  return [
    ...trackedApps.value.filter(a => !pendingRemoveIds.value.includes(a.id!)),
    ...pendingAddApps.value,
  ]
})

const excludedPaths = computed(() =>
  allTrackedApps.value.map(a => (a.exe_path || '').toLowerCase())
)

async function loadTrackedApps(projectId: number) {
  try {
    trackedApps.value = await invoke<TrackedApp[]>('get_tracked_apps', { projectId })
  } catch (e) {
    console.error('Failed to load tracked apps:', e)
    trackedApps.value = []
  }
}

function addTrackedAppFromPicker(app: { exe_name: string; exe_path: string; display_name: string; icon?: string | null }) {
  if (allTrackedApps.value.some(a => (a.exe_path || '').toLowerCase() === app.exe_path.toLowerCase())) return
  pendingAddApps.value.push({
    project_id: 0,
    exe_name: app.exe_name,
    exe_path: app.exe_path,
    display_name: app.display_name,
    icon: app.icon || undefined,
  })
}

async function browseForApp() {
  const result = await openFileDialog({
    multiple: false,
    filters: [{ name: 'Executables', extensions: ['exe', '*'] }],
  })
  if (!result) return
  const filePath = typeof result === 'string' ? result : String(result)
  const normalized = filePath.replace(/\//g, '\\')
  const exeName = normalized.split('\\').pop() || normalized
  const displayName = exeName.replace(/\.exe$/i, '')
  if (allTrackedApps.value.some(a => (a.exe_path || '').toLowerCase() === normalized.toLowerCase())) return
  pendingAddApps.value.push({
    project_id: 0,
    exe_name: exeName,
    exe_path: normalized,
    display_name: displayName,
  })
}

function removeTrackedApp(app: TrackedApp) {
  if (app.id) {
    pendingRemoveIds.value.push(app.id)
  } else {
    pendingAddApps.value = pendingAddApps.value.filter(a => a !== app)
  }
}

async function saveTrackedApps(projectId: number) {
  for (const id of pendingRemoveIds.value) {
    await invoke('remove_tracked_app', { id })
  }
  for (const app of pendingAddApps.value) {
    await invoke('add_tracked_app', { app: { ...app, project_id: projectId } })
  }
  pendingAddApps.value = []
  pendingRemoveIds.value = []
}

// Form data
const formData = reactive<Project>({
  name: '',
  client_id: undefined,
  hourly_rate: 0,
  color: '#D97706',
  archived: false,
  budget_hours: null,
  budget_amount: null,
  timeline_override: null,
  notes: null,
  currency: null,
})

// Budget status
interface BudgetStatus {
  hours_used: number
  amount_used: number
  budget_hours: number | null
  budget_amount: number | null
  percent_hours: number | null
  percent_amount: number | null
  daily_average_hours: number
  estimated_completion_days: number | null
  hours_remaining: number | null
  pace: string | null
}

const budgetStatus = ref<Record<number, BudgetStatus>>({})

// Task actuals (estimates vs actuals)
interface TaskActual {
  task_id: number
  task_name: string
  estimated_hours: number | null
  hourly_rate: number | null
  actual_hours: number
  variance: number | null
  progress: number | null
}

const taskActuals = ref<Record<number, TaskActual[]>>({})

async function loadBudgetStatus() {
  for (const project of projectsStore.projects) {
    if (project.id && project.budget_hours) {
      try {
        const today = new Date().toISOString().split('T')[0]
        const status = await invoke<BudgetStatus>('get_project_budget_status', { projectId: project.id, today })
        budgetStatus.value[project.id] = status
      } catch (e) {
        // ignore
      }
    }
  }
}

async function loadTaskActuals() {
  for (const project of projectsStore.projects) {
    if (!project.id || project.archived) continue
    try {
      const actuals = await invoke<TaskActual[]>('get_task_actuals', { projectId: project.id })
      if (actuals.length > 0) {
        taskActuals.value[project.id] = actuals
      }
    } catch {
      // ignore - project may have no tasks
    }
  }
}

function getBudgetUsed(project: any): number {
  return budgetStatus.value[project.id]?.hours_used || 0
}

function getBudgetForecast(project: any): string | null {
  const status = budgetStatus.value[project.id]
  if (!status?.pace) return null
  const remaining = status.hours_remaining
  if (remaining === null || remaining === undefined) return null
  if (status.pace === 'complete') return 'Budget reached'
  const days = status.estimated_completion_days
  if (days === null || days === undefined || days < 0) return `${formatNumber(remaining, 0)}h remaining - no recent activity`
  const avgText = status.daily_average_hours > 0 ? `${formatNumber(status.daily_average_hours, 1)}h/day avg` : ''
  if (status.pace === 'ahead') return `${formatNumber(remaining, 0)}h remaining - ahead of schedule${avgText ? ' (' + avgText + ')' : ''}`
  if (status.pace === 'on_track') return `${formatNumber(remaining, 0)}h remaining - on track${avgText ? ' (' + avgText + ')' : ''}`
  return `${formatNumber(remaining, 0)}h remaining - behind schedule${avgText ? ' (' + avgText + ')' : ''}`
}

function getBudgetPct(project: any): number {
  if (!project.budget_hours) return 0
  const used = getBudgetUsed(project)
  return (used / project.budget_hours) * 100
}

// Project health computation
function getProjectHealth(project: Project, status: BudgetStatus): { status: string; icon: string; color: string } {
  if (!status) return { status: 'No budget', icon: 'none', color: 'text-text-tertiary' }
  const pct = project.budget_hours ? (status.hours_used / project.budget_hours) * 100 : 0
  const pace = status.pace

  if (pct > 100) return { status: 'Over Budget', icon: 'error', color: 'text-status-error-text' }
  if (pace === 'behind' && pct > 75) return { status: 'At Risk', icon: 'warning', color: 'text-status-warning' }
  return { status: 'On Track', icon: 'success', color: 'text-status-running' }
}

const attentionProjects = computed(() => {
  return projectsStore.projects
    .filter(p => !p.archived && p.id && budgetStatus.value[p.id!])
    .map(p => ({
      project: p,
      health: getProjectHealth(p, budgetStatus.value[p.id!])
    }))
    .filter(ap => ap.health.icon === 'warning' || ap.health.icon === 'error')
})

// Get client name by ID
function getClientName(clientId?: number): string {
  if (!clientId) return 'No client'
  const client = clientsStore.clients.find(c => c.id === clientId)
  return client?.name || 'Unknown client'
}

// Open create dialog
function openCreateDialog() {
  editingProject.value = null
  formData.name = ''
  formData.client_id = undefined
  formData.hourly_rate = parseFloat(settingsStore.settings.hourly_rate) || 0
  formData.color = '#F59E0B'
  formData.archived = false
  formData.budget_hours = null
  formData.budget_amount = null
  formData.timeline_override = null
  formData.notes = null
  formData.currency = null
  billingType.value = 'hourly'
  trackedApps.value = []
  pendingAddApps.value = []
  pendingRemoveIds.value = []
  trackedAppsExpanded.value = false
  projectTasksList.value = []
  pendingNewTasks.value = []
  pendingRemoveTaskIds.value = []
  newTaskName.value = ''
  tasksExpanded.value = false
  snapshotForm(getFormData())
  showDialog.value = true
}

// Open edit dialog
async function openEditDialog(project: Project) {
  editingProject.value = project
  formData.id = project.id
  formData.name = project.name
  formData.client_id = project.client_id
  formData.hourly_rate = project.hourly_rate
  formData.color = project.color
  formData.archived = project.archived
  formData.budget_hours = project.budget_hours ?? null
  formData.budget_amount = project.budget_amount ?? null
  formData.timeline_override = project.timeline_override ?? null
  formData.notes = project.notes ?? null
  formData.currency = project.currency ?? null
  // Infer billing type from existing data
  billingType.value = (project.budget_amount && project.budget_amount > 0) ? 'fixed' : 'hourly'
  pendingAddApps.value = []
  pendingRemoveIds.value = []
  trackedAppsExpanded.value = false
  projectTasksList.value = []
  pendingNewTasks.value = []
  pendingRemoveTaskIds.value = []
  newTaskName.value = ''
  tasksExpanded.value = false
  if (project.id) {
    await loadTrackedApps(project.id)
    await loadProjectTasks(project.id)
    if (trackedApps.value.length > 0) trackedAppsExpanded.value = true
    if (projectTasksList.value.length > 0) tasksExpanded.value = true
  } else {
    trackedApps.value = []
  }
  snapshotForm(getFormData())
  showDialog.value = true
}

// Close dialog
function closeDialog() {
  showDialog.value = false
  editingProject.value = null
}

// Handle form submit
async function handleSubmit() {
  // Clear irrelevant billing fields based on type
  if (billingType.value === 'hourly') {
    formData.budget_amount = null
  } else {
    formData.hourly_rate = 0
  }

  let projectId: number | undefined
  if (editingProject.value) {
    await projectsStore.updateProject({ ...formData })
    projectId = formData.id
  } else {
    projectId = (await projectsStore.createProject({ ...formData })) ?? undefined
  }
  if (projectId) {
    await saveTrackedApps(projectId)
    await saveTasks(projectId)
  }
  closeDialog()
}

// Confirm delete
async function confirmDelete(project: Project) {
  if (!project.id) return
  projectToDelete.value = project

  try {
    const deps = await invoke<any>('get_project_dependents', { projectId: project.id })
    const total = deps.time_entries + deps.favorites + deps.expenses + deps.recurring_entries + deps.timeline_events + deps.tasks + deps.tracked_apps

    if (total > 0) {
      cascadeImpacts.value = [
        { label: 'time entries', count: deps.time_entries },
        { label: 'favorites', count: deps.favorites },
        { label: 'expenses', count: deps.expenses },
        { label: 'recurring entries', count: deps.recurring_entries },
        { label: 'timeline events', count: deps.timeline_events },
        { label: 'tasks', count: deps.tasks },
        { label: 'tracked apps', count: deps.tracked_apps },
      ]
      showCascadeDelete.value = true
    } else {
      showDeleteDialog.value = true
    }
  } catch {
    showDeleteDialog.value = true
  }
}

// Cancel delete
function cancelDelete() {
  showDeleteDialog.value = false
  projectToDelete.value = null
}

// Handle delete
async function handleDelete() {
  if (projectToDelete.value?.id) {
    await projectsStore.deleteProject(projectToDelete.value.id)
  }
  cancelDelete()
}

// Handle cascade delete confirmation
async function handleCascadeConfirm() {
  try {
    await handleDelete()
  } catch {
    const toast = useToastStore()
    toast.error('Failed to delete project')
  }
  showCascadeDelete.value = false
}

// Load data on mount
onMounted(async () => {
  await Promise.all([
    projectsStore.fetchProjects(),
    clientsStore.fetchClients(),
    settingsStore.fetchSettings()
  ])
  await loadBudgetStatus()
  await loadTaskActuals()

  // Handle ?edit=<id> query from ProjectDetail
  const editId = route.query.edit
  if (editId) {
    const project = projectsStore.projects.find(p => p.id === Number(editId))
    if (project) openEditDialog(project)
    router.replace({ path: '/projects' })
  }
})
</script>
