import { CommonModule } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  ContentChild,
  TemplateRef,
  input,
  output,
  signal,
  effect,
  computed,
} from '@angular/core';

// --- Interfaces (kept outside the component class) ---
export interface TableColumn<T = any> {
  key: string;
  label: string;
  sortable?: boolean;
  width?: string;
  cellTemplate?: TemplateRef<{ $implicit: T }>;
  cell?: (row: T, index?: number) => string | number | null | undefined;
  defaultVisible?: boolean;
}

export interface SortState {
  key: string;
  dir: 'asc' | 'desc' | '';
}
// -----------------------------------------------------

@Component({
  selector: 'app-data-table',
  standalone: true, // Assuming standalone component structure
  imports: [CommonModule],
  templateUrl: './data-table.html',
  styleUrls: ['./data-table.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DataTable<T extends Record<string, any>> {
  // --- Inputs ---
  columns = input<TableColumn<T>[]>([]);
  data = input<T[]>([]);
  loading = input(false);
  sort = input<SortState>({ key: '', dir: '' });
  enableColumnChooser = input<boolean>(true);
  persistenceKey = input<string | null>(null);
  minVisible = input<number>(1);
  visibleKeys = input<string[] | null>(null);
  actionsPosition = input<'left' | 'right' | 'none'>('right');
  actionsSticky = input<boolean>(true);
  actionsHeader = input<string>('Actions');

  // --- Outputs ---
  sortChange = output<SortState>();
  visibleKeysChange = output<string[]>();

  // --- Content Projection ---
  @ContentChild('rowActions', { read: TemplateRef }) rowActionsTpl?: TemplateRef<any>;

  // --- Internal State ---
  protected _visible = signal<string[]>([]);
  protected showChooser = signal<boolean>(false);

  // --- Core Reactivity: The Fix ---
  /** Computed signal that filters the columns based on the current internal visible keys. */
  readonly effectiveColumns = computed<TableColumn<T>[]>(() => {
    const cols = this.columns() ?? [];
    const vk = this._visible(); // Dependency: Updates when visible keys change

    // If no columns are defined or no keys are marked visible, return empty.
    if (!cols.length || !vk || vk.length === 0) return [];

    const set = new Set(vk);
    // Filter the original column list to maintain the correct order
    return cols.filter((c) => set.has(c.key));
  });

  // Computed Signal for Colspan (used for empty/loading row)
  readonly colSpan = computed<number>(() => {
    const hasActions = this.rowActionsTpl && this.actionsPosition() !== 'none';
    return this.effectiveColumns().length + (hasActions ? 1 : 0);
  });

  private get isControlled(): boolean {
    return this.visibleKeys() !== null && this.visibleKeys() !== undefined;
  }

  constructor() {
    // 1. Initialize visibility (loads defaults or persisted state)
    effect(
      () => {
        this.columns(); // Re-run when column definition changes
        this.loadInitialVisible();
      },
      { allowSignalWrites: true }
    );

    // 2. Handle controlled mode changes
    effect(() => {
      if (!this.isControlled) return;
      const external = this.visibleKeys() ?? [];
      const cols = this.columns() ?? [];
      const allowed = new Set(cols.map((c) => c.key));
      const sanitized = external.filter((k) => allowed.has(k));

      // ⬇️ Only mirror if it actually differs from our current optimistic state
      if (!this.arraysEqual(this._visible(), sanitized)) {
        this._visible.set(sanitized);
      }
    });

    // 3. Persist and reset sort when internal state changes (in uncontrolled mode)
    effect(() => {
      const current = this._visible(); // dep
      if (!this.isControlled) {
        this.persistVisible(current);
      }
      this.resetSortIfHidden();
    });
  }

  private arraysEqual(a: string[], b: string[]) {
    if (a === b) return true;
    if (!a || !b || a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
    return true;
  }

  // --- Methods ---
  toggleSort(c: TableColumn<T>) {
    // ... (Sorting logic remains the same)
    if (!c.sortable) return;
    const currentSort = this.sort();
    let nextDir: 'asc' | 'desc' | '' = 'asc';
    if (currentSort.key === c.key) {
      if (currentSort.dir === 'asc') nextDir = 'desc';
      else if (currentSort.dir === 'desc') nextDir = '';
    }
    const newSortState: SortState = { key: nextDir ? c.key : '', dir: nextDir };
    this.sortChange.emit(newSortState);
  }

  protected loadInitialVisible() {
    const cols = this.columns() ?? [];
    if (!cols.length) {
      this._visible.set([]);
      return;
    }

    if (this.isControlled) {
      const external = this.visibleKeys() ?? [];
      const allowed = new Set(cols.map((c) => c.key));
      const sanitized = external.filter((k) => allowed.has(k));
      this._visible.set(sanitized);
      return;
    }

    const key = this.persistenceKey();
    if (key) {
      try {
        const raw = localStorage.getItem(key);
        if (raw) {
          const parsed = JSON.parse(raw) as { visibleKeys?: string[] };
          const list = (parsed?.visibleKeys ?? []).filter((k) => cols.some((c) => c.key === k));
          if (list.length) {
            this._visible.set(list);
            return;
          }
        }
      } catch {}
    }

    const defaults = cols.filter((c) => c.defaultVisible).map((c) => c.key);
    if (defaults.length) {
      this._visible.set(defaults);
      return;
    }

    this._visible.set(cols.map((c) => c.key));
  }

  protected persistVisible(current: string[]) {
    const key = this.persistenceKey();
    if (!key) return;
    try {
      localStorage.setItem(key, JSON.stringify({ visibleKeys: current }));
    } catch {}
  }

  toggleColumn(key: string) {
    const cols = this.columns() ?? [];
    const allowed = new Set(cols.map((c) => c.key));
    if (!allowed.has(key)) return;

    const current = this._visible();
    const isOn = current.includes(key);

    let next: string[];
    if (isOn) {
      if (current.length <= this.minVisible()) return; // keep at least minVisible
      next = current.filter((k) => k !== key);
    } else {
      next = [...current, key];
    }

    // ⬇️ Optimistic update so the UI updates immediately
    this._visible.set(next);

    if (this.isControlled) {
      // Inform parent; if parent reflects it back, our effect won't stomp since arraysEqual
      this.visibleKeysChange.emit(next);
    } else {
      // Uncontrolled: persistence handled by the effect that watches _visible()
      // (nothing else to do)
    }
  }

  showAllColumns() {
    const next = (this.columns() ?? []).map((c) => c.key);
    this._visible.set(next); // optimistic
    if (this.isControlled) this.visibleKeysChange.emit(next);
  }

  hideAllColumnsExceptFirst() {
    const cols = this.columns() ?? [];
    if (!cols.length) return;
    const next = [cols[0].key];
    this._visible.set(next);
    if (this.isControlled) this.visibleKeysChange.emit(next);
  }

  protected resetSortIfHidden() {
    const s = this.sort();
    if (!s?.key) return;
    const visibleSet = new Set(this._visible());
    if (!visibleSet.has(s.key)) {
      this.sortChange.emit({ key: '', dir: '' });
    }
  }

  getNestedValue(obj: any, path: string): any {
    if (!obj || !path) return '';
    return path
      .split('.')
      .reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : ''), obj);
  }

  actionsThClasses(): string {
    const sticky = this.actionsSticky()
      ? 'sticky z-10 bg-white dark:bg-gray-800/80 backdrop-blur-sm transition-all duration-300'
      : '';
    return this.actionsPosition() === 'right'
      ? `${sticky} right-0 px-5 py-3`
      : `${sticky} left-0 px-5 py-3`;
  }

  actionsTdClasses(): string {
    const sticky = this.actionsSticky()
      ? 'sticky z-10 bg-white dark:bg-gray-900 border-l border-gray-100 dark:border-gray-800/50'
      : '';
    return this.actionsPosition() === 'right'
      ? `${sticky} right-0 p-4 text-right align-middle`
      : `${sticky} left-0 p-4 align-middle`;
  }
}
