import { DosyaBoyutuPipe } from './pipes/dosya-boyutu.pipe';
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { NzTooltipModule } from 'ng-zorro-antd/tooltip';
import { FormsModule } from '@angular/forms';
import { HttpClient, HttpEventType } from '@angular/common/http';
import { NzProgressComponent } from "ng-zorro-antd/progress";
import { FileBrowserService } from './services/file-browser.service';
import { seansDeposu, KayitliSeans, gecmisDeposu, GecmisKaydi } from './services/seans-deposu';
import { NzPaginationComponent } from 'ng-zorro-antd/pagination';
import { NzGridModule } from 'ng-zorro-antd/grid';
import { NzSwitchModule } from 'ng-zorro-antd/switch';
import { NzButtonModule } from 'ng-zorro-antd/button';

interface PostFile {
  id: string;
  name: string;
  cleanedContent: string;
  workOffset: string;
  size: number;
  xMin: number | null;
  xMax: number | null;
  yMin: number | null;
  yMax: number | null;
  zMin: number | null;
  zMax: number | null;
  zLast: number | null;
  customZLast?: number | null;
  kutukZ?: number | null;
  baglamaYonu?: string | null;
  onMu?: boolean | null;
  takimSatiri?:string | null;
  takimCapi?: number | null;
  baglamaYonuOrijinal?: string | null;
  kutukZOrijinal?: number | null;
  onMuOrijinal?: boolean | null;
  takimSatiriOrijinal?: string | null;
  kaynakYol?: string;
}

interface RecentFile {
  name: string;
  url: string;
  size: number;
  date_modified: string;
  selected: boolean;
  progress: number;
  progressVisible: boolean;
  mesgul: boolean;
}

interface NcMove {
  from: { x: number; y: number; z: number };
  to: { x: number; y: number; z: number };
  distance: number;
  mode: 'G0' | 'G1';
  feed: number;
  time: number;
}

interface NcTimeResult {
  totalTimeMin: number;
  totalTimeSec: number;
  rapidTimeMin: number;
  rapidTimeSec: number;
  cuttingTimeMin: number;
  cuttingTimeSec: number;
  totalMoves: number;
  moves: NcMove[];
}

interface Bounds {
  xMin: number | null;
  xMax: number | null;
  yMin: number | null;
  yMax: number | null;
  zMin: number | null;
  zMax: number | null;
  zLast: number | null;
}

@Component({
  selector: 'app-post-birlestiricisi',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    DosyaBoyutuPipe,
    NzProgressComponent,
    NzPaginationComponent,
    NzGridModule,
    NzSwitchModule,
    NzButtonModule,
    NzTooltipModule
  ],
  templateUrl: './post-birlestiricisi.component.html',
  styleUrls: ['./post-birlestiricisi.component.less'],
  providers: [FileBrowserService],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PostBirlestiricisiComponent {
  files: PostFile[] = [];
  readonly availableOffsets = ['G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G59.1', 'G59.2', 'G59.3', 'G59.4', 'G59.5', 'G59.6'] as const;
  mergedResult = '';
  mergedBounds: Bounds = this.createEmptyBounds();
  programNumber = 1000;
  programIsmi = "BIRLESIK";
  readonly headerLines = ['G90', 'G40', 'G80'] as const;

  isDragging = false;
  progressVisible = false;
  progressPercent = 0;
  currentFileName = '';

  mergeInProgress = false;
  mergeProgress = 0;
  mergeStatus = '';

  recentFiles: RecentFile[] = [];
  showRecentFiles = false;
  loadingRecentFiles = false;
  recentFilesError = '';

  basliklariYaz: boolean = true;
  limitKontrolYap: boolean = true;

  total = 0;
  pageIndex = 1;
  pageSize = 5;




  // Regex patterns - compile once
  private readonly gCodePattern = /G(\d+)/g;
  private readonly fPattern = /F([\d.]+)/;
  private readonly xPattern = /X(-?\d+\.?\d*)/i;
  private readonly yPattern = /Y(-?\d+\.?\d*)/i;
  private readonly zPattern = /Z(-?\d+\.?\d*)/i;
  private readonly commentPattern = /\([^)]*\)/g;
  private readonly workOffsetPattern = /G5[4-9](\.\d+)?/gi;

  constructor(
    private http: HttpClient,
    public everything: FileBrowserService,
    private CD: ChangeDetectorRef
  ) { }

  ngAfterViewInit() {
    setTimeout(() => this.loadRecentFiles(), 0);
    this.usbIzle();
    this.seanslariYukle();
    this.gecmisYukle();
  }

  // Event Handlers
  onDrop(event: DragEvent) {
    event.preventDefault();
    this.isDragging = false;
    const dt = event.dataTransfer;
    if (dt?.files) {
      this.readMultipleFiles(dt.files);
    }
  }

  onDragOver(event: DragEvent) {
    event.preventDefault();
    this.isDragging = true;
  }

  onDragLeave(event: DragEvent) {
    event.preventDefault();
    this.isDragging = false;
  }

  onFileInputChange(event: Event) {
    const input = event.target as HTMLInputElement;
    if (input.files) {
      this.readMultipleFiles(input.files);
      input.value = '';
    }
  }

  // Recent Files
  async loadRecentFiles() {
    this.showRecentFiles = true;
    this.loadingRecentFiles = true;
    this.recentFilesError = '';
    this.degisti();

    try {
      const response: any = await this.everything.search(this.aramaSorgusu(), this.pageIndex - 1, this.pageSize);
      this.total = response.total;

      if (response?.files && Array.isArray(response.files)) {
        this.recentFiles = response.files.map((file: any) => ({
          name: file.name || '',
          url: `${file.path || ''}/${file.name || ''}`,
          size: parseInt(file.size || 0) || 0,
          date_modified: file.dateModified,
          selected: false,
          progress: 0,
          progressVisible: false,
          mesgul: false
        }));
      } else {
        this.recentFilesError = 'Dosya listesi alinamadi';
      }
    } catch (error: any) {
      console.error('Recent files loading error:', error);
      this.recentFilesError = error.status === 0
        ? 'Baglanti hatasi - HTTPS sitesinden HTTP endpoint\'e erisilemez.'
        : `Dosyalar yuklenirken hata olustu: ${error.message || ''}`;
    } finally {
      this.loadingRecentFiles = false;
      this.degisti();
    }
  }

  aramaMetni = '';

  /** Kutudaki metin Everything sorgusuna çevrilir; uzantı belirtilmemişse yalnızca .nc aranır. */
  aramaSorgusu(): string {
    const metin = this.aramaMetni.trim();
    if (!metin) return '*.nc';
    return /\.nc\b/i.test(metin) ? metin : `${metin} *.nc`;
  }

  private aramaZamanlayici?: ReturnType<typeof setTimeout>;

  aramaYap() {
    clearTimeout(this.aramaZamanlayici);
    this.pageIndex = 1;
    this.loadRecentFiles();
  }

  /** Yazarken arama: son tuş vuruşundan 400 ms sonra çalışır. */
  aramaGirdiDegisti() {
    clearTimeout(this.aramaZamanlayici);
    this.aramaZamanlayici = setTimeout(() => this.aramaYap(), 400);
  }

  private readonly usbBaz = 'http://127.0.0.1:7777';
  usbSurucular: string[] = [];
  usbDosyalar: Record<string, { name: string; size: number; secili: boolean }[]> = {};
  usbMesaj = '';
  private usbZamanlayici?: ReturnType<typeof setInterval>;

  usbIzle() {
    this.usbYenile();
    this.usbZamanlayici = setInterval(() => this.usbYenile(), 3000);
  }

  ngOnDestroy() {
    clearInterval(this.usbZamanlayici);
  }

  async usbYenile() {
    let liste: string[] = [];
    try {
      const yanit = await fetch(`${this.usbBaz}/usb`);
      liste = yanit.ok ? await yanit.json() : [];
    } catch {
      liste = [];
    }
    const surucuDegisti = liste.join() !== this.usbSurucular.join();
    if (surucuDegisti) {
      this.usbSurucular = liste;
      const temiz: typeof this.usbDosyalar = {};
      for (const s of liste) if (this.usbDosyalar[s]) temiz[s] = this.usbDosyalar[s];
      this.usbDosyalar = temiz;
      this.degisti();
    }
    await Promise.all(liste.map(s => this.usbNcYenile(s)));
  }

  private async usbNcYenile(surucu: string) {
    try {
      const yanit = await fetch(`${this.usbBaz}/usb/nc?surucu=${encodeURIComponent(surucu)}`);
      const liste: { name: string; size: number }[] = yanit.ok ? await yanit.json() : [];
      const onceki = new Map((this.usbDosyalar[surucu] ?? []).map(d => [d.name, d.secili]));
      const yeni = liste.map(d => ({ name: d.name, size: d.size, secili: onceki.get(d.name) ?? false }));
      if (JSON.stringify(yeni) !== JSON.stringify(this.usbDosyalar[surucu] ?? [])) {
        this.usbDosyalar = { ...this.usbDosyalar, [surucu]: yeni };
        this.degisti();
      }
    } catch {
      // ağ hatasında mevcut liste korunur
    }
  }

  usbYuklemeSurucu = '';
  usbYuklemeYuzde = 0;

  usbGonder(surucu: string) {
    if (!this.mergedResult) {
      this.usbMesaj = 'Önce dosyaları birleştirin';
      this.degisti();
      return;
    }
    const ad = `${this.programIsmi || 'BIRLESIK'}.NC`;
    const xhr = new XMLHttpRequest();
    this.usbYuklemeSurucu = surucu;
    this.usbYuklemeYuzde = 0;
    this.usbMesaj = '';
    this.degisti();

    const bitir = () => {
      setTimeout(() => {
        this.usbYuklemeSurucu = '';
        this.degisti();
      }, 1000);
    };

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable) {
        this.usbYuklemeYuzde = Math.round((e.loaded / e.total) * 100);
        this.degisti();
      }
    };
    xhr.onload = () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        try {
          this.usbMesaj = `${surucu} sürücüsüne yazıldı: ${JSON.parse(xhr.responseText).dosya}`;
        } catch {
          this.usbMesaj = `${surucu} sürücüsüne yazıldı`;
        }
        void this.gecmisKaydet(`USB ${surucu}`);
      } else {
        this.usbMesaj = `USB'ye yazılamadı: ${xhr.responseText || xhr.status}`;
      }
      this.usbYuklemeYuzde = 100;
      this.degisti();
      bitir();
    };
    xhr.onerror = () => {
      this.usbMesaj = 'USB\'ye yazılamadı: bağlantı hatası';
      bitir();
    };
    xhr.open('POST', `${this.usbBaz}/usb/yaz?surucu=${encodeURIComponent(surucu)}&ad=${encodeURIComponent(ad)}`);
    xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
    xhr.send(this.mergedResult);
  }

  async usbSecilenleriSil(surucu: string) {
    const secili = (this.usbDosyalar[surucu] ?? []).filter(d => d.secili).map(d => d.name);
    if (!secili.length) return;
    if (!window.confirm(`${surucu} sürücüsünden ${secili.length} dosya silinecek. Bu işlem geri alınamaz. Devam edilsin mi?`)) return;
    try {
      const yanit = await fetch(`${this.usbBaz}/usb/sil`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ surucu, dosyalar: secili }),
      });
      const sonuc: { name: string; ok: boolean }[] = yanit.ok ? await yanit.json() : [];
      const basarili = sonuc.filter(s => s.ok).length;
      const hatali = sonuc.length - basarili;
      this.usbMesaj = `${basarili} dosya silindi${hatali ? `, ${hatali} hata` : ''}`;
    } catch {
      this.usbMesaj = 'Silme işlemi başarısız';
    }
    await this.usbNcYenile(surucu);
    this.degisti();
  }

  toggleRecentFile(file: RecentFile) {
    file.selected = !file.selected;
    this.degisti();
  }

  async addSelectedRecentFiles() {
    const selectedFiles = this.recentFiles.filter(f => f.selected);
    if (!selectedFiles.length) return;

    this.loadingRecentFiles = true;
    this.degisti();

    try {
      for (const recentFile of selectedFiles) {
        this.subscribeToFileDownload(recentFile);
      }
    } finally {
      this.loadingRecentFiles = false;
      this.showRecentFiles = false;
      this.recentFiles = [];
      this.progressVisible = false;
      this.degisti();
    }
  }

  // Everything sonuclarinda "Ekle" ile eklenen dosyalarin son ekleme tarihi (yol -> ISO tarih).
  private readonly eklenmeAnahtari = 'fikoyt-post-eklenme';
  private eklenmeKayitlari: Record<string, string> = this.eklenmeOku();

  private eklenmeOku(): Record<string, string> {
    try {
      return JSON.parse(localStorage.getItem(this.eklenmeAnahtari) || '{}');
    } catch {
      return {};
    }
  }

  /** Dosya daha once "Ekle" ile eklendiyse son ekleme tarihini doner; yoksa null. */
  eklenmeTarihi(file: RecentFile): string | null {
    return this.eklenmeKayitlari[file.url] ?? null;
  }

  private eklenmeKaydet(file: RecentFile): void {
    this.eklenmeKayitlari = { ...this.eklenmeKayitlari, [file.url]: new Date().toISOString() };
    try {
      localStorage.setItem(this.eklenmeAnahtari, JSON.stringify(this.eklenmeKayitlari));
    } catch {
      // Depolama kapaliysa kayit yalnizca bu oturumda gorunur.
    }
  }

  /** Dosyanin "Eklendi" bilgisini kaldirir. */
  eklenmeSil(file: RecentFile): void {
    const { [file.url]: _silinen, ...kalan } = this.eklenmeKayitlari;
    this.eklenmeKayitlari = kalan;
    try {
      localStorage.setItem(this.eklenmeAnahtari, JSON.stringify(kalan));
    } catch {
      // Depolama kapaliysa yalnizca bu oturumda kaldirilir.
    }
    this.degisti();
  }

  async addFileFromUrl(recentFile: RecentFile) {
    this.eklenmeKaydet(recentFile);
    recentFile.progressVisible = true;
    recentFile.progress = 0;
    recentFile.mesgul = true;
    this.degisti();

    this.subscribeToFileDownload(recentFile, () => {
      setTimeout(() => {
        recentFile.progressVisible = false;
        recentFile.mesgul = false;
        this.degisti();
      }, 750);
    });
  }

  private subscribeToFileDownload(recentFile: RecentFile, onComplete?: () => void) {
    this.http.get(`http://127.0.0.1:7777/dosya?yol=${encodeURIComponent(recentFile.url)}`, {
      responseType: 'blob',
      reportProgress: true,
      observe: 'events'
    }).subscribe({
      next: async (event) => {
        if (event.type === HttpEventType.DownloadProgress) {
          recentFile.progress = Math.round((event.loaded / (event.total || event.loaded)) * 100);
          this.degisti();
        } else if (event.type === HttpEventType.Response) {
          const response = event.body as Blob;
          const file = new File([response], recentFile.name, { type: 'text/plain' });
          const postFile = await this.readFile(file);
          postFile.kutukZ = this.tahminiKutukZ(postFile);
          postFile.baglamaYonu = this.baglamaYonu(postFile);
          postFile.onMu = this.OnIsl(postFile);
          postFile.takimCapi = this.takimCapiAl(postFile.takimSatiri);
          postFile.kaynakYol = recentFile.url;
          this.files = [...this.files, postFile];
          this.degisti();
          onComplete?.();
        }
      },
      error: (error) => {
        console.error(`Error loading ${recentFile.name}:`, error);
        recentFile.progressVisible = false;
        recentFile.progress = 0;
        recentFile.mesgul = false;
        this.degisti();
      }
    });
  }

  cancelRecentFiles() {
    this.showRecentFiles = false;
    this.recentFiles = [];
    this.degisti();
  }

  // File Reading
  private async readMultipleFiles(fileList: FileList) {
    const tasks: Promise<PostFile>[] = [];
    for (let i = 0; i < fileList.length; i++) {
      const f = fileList.item(i);
      if (f) tasks.push(this.readFile(f));
    }

    const results = await Promise.all(tasks);
    this.files = [...this.files, ...results];
    this.progressVisible = false;

    for (let i = 0; i < this.files.length; i++) {
      const f = this.files[i];
      f.kutukZ = this.tahminiKutukZ(f);
      f.baglamaYonu = this.baglamaYonu(f);
      f.onMu = this.OnIsl(f);
      f.takimCapi = this.takimCapiAl(f.takimSatiri);
    }
    this.degisti();
  }

  /**
   * "25 kr" | "25K" | "25k" | "16kr" | "20 mm" gibi duzensiz takim
   * ifadelerinden capi cikarir. Ilk harfe rastlayana kadar (yoksa sonuna
   * kadar) sayiyi okur. Gecersizse null doner.
   */
  takimCapiAl(satir: string | null | undefined): number | null {
    if (!satir) return null;

    const s = satir.toString().trim().replace(',', '.');

    // Harfle basliyorsa sayi yok say
    if (/^\s*[a-zA-Z]/.test(s)) return null;

    const eslesme = s.match(/^\s*(\d+(?:\.\d+)?)/);
    if (!eslesme) return null;

    const sonuc = parseFloat(eslesme[1]);
    return isNaN(sonuc) ? null : sonuc;
  }

  /**
   * Dosyalardaki takim caplarini karsilastirir.
   * Hepsi ayni (veya hicbiri okunamiyorsa) -> true,
   * iclerinden en az biri farkli -> false doner.
   */
  takimCapiUyumlu(): boolean {
    const caplar = this.files
      .map(f => f.takimCapi)
      .filter((c): c is number => c !== null && c !== undefined);
    if (caplar.length === 0) return true;
    return caplar.every(c => c === caplar[0]);
  }

  /** Farkli takim capi varsa dosya adlariyla birlikte doner (bilgi amacli) */
  farkliTakimCaplari(): { cap: number; files: string[] }[] {
    const map = new Map<number, string[]>();
    for (const f of this.files) {
      if (f.takimCapi == null) continue;
      const mevcut = map.get(f.takimCapi) || [];
      mevcut.push(f.name);
      map.set(f.takimCapi, mevcut);
    }
    return [...map.entries()].map(([cap, files]) => ({ cap, files }));
  }

  private async readFile(file: File): Promise<PostFile> {
    this.progressVisible = false;
    this.progressPercent = 0;
    this.currentFileName = file.name;
    this.degisti();

    const reader = file.stream().getReader();
    const decoder = new TextDecoder("utf-8");
    let { value, done } = await reader.read();
    let buffer = '';
    const cleaned: string[] = [];
    let loadedBytes = 0;
    const totalBytes = file.size;
    const bounds = this.createEmptyBounds();

    while (!done) {
      buffer += decoder.decode(value, { stream: true });
      loadedBytes += value?.length || 0;

      this.progressPercent = Math.min(100, Math.floor((loadedBytes / totalBytes) * 100));
      this.degisti();

      const lines = buffer.split(/\r?\n/);
      buffer = lines.pop() || '';

      this.processLines(lines, cleaned, bounds);

      ({ value, done } = await reader.read());
    }

    if (buffer.trim()) {
      this.processLines([buffer.trim()], cleaned, bounds);
    }

    const finalLines = this.removeProgramEnd(cleaned);

    this.progressVisible = false;
    this.progressPercent = 0;
    this.degisti();

    return {
      id: this.makeId(),
      name: file.name,
      cleanedContent: finalLines.join('\n'),
      workOffset: this.availableOffsets[0],
      size: file.size,
      takimSatiri: (finalLines[4] ?? '').replace("//(","").replace(")","").trim(),
      ...bounds
    };
  }

  private processLines(lines: string[], cleaned: string[], bounds: Bounds) {
    for (let line of lines) {
      line = line.trim();
      if (!line || line[0] === 'O' || line[0] === '%') continue;
      if (line.startsWith('M30') || line.startsWith('M99')) continue;

      if (line.includes('(')) {
        line = line.replace('(', '//(');
      }

      this.extractCoordinates(line, bounds);

      line = line.replace(this.workOffsetPattern, '').replace(/\s+/g, ' ').trim();
      if (line) cleaned.push(line);
    }
  }

  private extractCoordinates(line: string, bounds: Bounds) {
    let cleanLine = line
      .replace(this.commentPattern, '')
      .replace(/\/\/.*$/, '');

    const xMatch = cleanLine.match(this.xPattern);
    if (xMatch) {
      const x = parseFloat(xMatch[1]);
      if (!isNaN(x)) {
        bounds.xMin = bounds.xMin === null ? x : Math.min(bounds.xMin, x);
        bounds.xMax = bounds.xMax === null ? x : Math.max(bounds.xMax, x);
      }
    }

    const yMatch = cleanLine.match(this.yPattern);
    if (yMatch) {
      const y = parseFloat(yMatch[1]);
      if (!isNaN(y)) {
        bounds.yMin = bounds.yMin === null ? y : Math.min(bounds.yMin, y);
        bounds.yMax = bounds.yMax === null ? y : Math.max(bounds.yMax, y);
      }
    }

    const zMatch = cleanLine.match(this.zPattern);
    if (zMatch) {
      const z = parseFloat(zMatch[1]);
      if (!isNaN(z)) {
        bounds.zMin = bounds.zMin === null ? z : Math.min(bounds.zMin, z);
        bounds.zMax = bounds.zMax === null ? z : Math.max(bounds.zMax, z);
        bounds.zLast = z;
      }
    }
  }

  private removeProgramEnd(lines: string[]): string[] {
    while (lines.length) {
      const t = lines[lines.length - 1].trim();
      if (/^N\d+\s+M(2|5|30)\b/i.test(t) || /^M(2|5|30)$/i.test(t)) {
        lines.pop();
      } else {
        break;
      }
    }
    return lines;
  }

  private makeId(): string {
    return Math.random().toString(36).slice(2, 11);
  }

  // File Management
  moveUp(index: number) {
    if (index <= 0) return;
    [this.files[index - 1], this.files[index]] = [this.files[index], this.files[index - 1]];
    this.files = [...this.files];
    this.clearMerge();
  }

  moveDown(index: number) {
    if (index >= this.files.length - 1) return;
    [this.files[index], this.files[index + 1]] = [this.files[index + 1], this.files[index]];
    this.files = [...this.files];
    this.clearMerge();
  }

  removeFile(index: number) {
    this.files.splice(index, 1);
    this.files = [...this.files];
    this.clearMerge();
  }

  updateOffset(index: number, offset: string) {
    this.files[index].workOffset = offset;
    this.files = [...this.files];
    this.clearMerge();
  }

  private clearMerge() {
    this.mergedResult = '';
    this.resetMergedBounds();
    this.degisti();
  }

  // NC Time Calculation
  calculateNcTime(
    ncCode: string,
    rapidFeed: number = 15000,
    defaultFeed: number = 1000
  ): NcTimeResult {
    const lines = ncCode.trim().split('\n');
    let currentX = 0.0, currentY = 0.0, currentZ = 0.0;
    let currentFeed = defaultFeed;
    let currentMode: 'G0' | 'G1' = 'G0';
    let totalTime = 0.0, rapidTime = 0.0, cuttingTime = 0.0;
    const moves: NcMove[] = [];

    for (const line of lines) {
      const cleanLine = line.replace(this.commentPattern, '').trim();
      if (!cleanLine) continue;

      const gMatches = cleanLine.matchAll(this.gCodePattern);
      for (const match of gMatches) {
        const gCode = match[1];
        if (gCode === '0' || gCode === '00') currentMode = 'G0';
        else if (gCode === '1' || gCode === '01') currentMode = 'G1';
      }

      const fMatch = cleanLine.match(this.fPattern);
      if (fMatch) currentFeed = parseFloat(fMatch[1]);

      const xMatch = cleanLine.match(this.xPattern);
      const yMatch = cleanLine.match(this.yPattern);
      const zMatch = cleanLine.match(this.zPattern);

      const newX = xMatch ? parseFloat(xMatch[1]) : currentX;
      const newY = yMatch ? parseFloat(yMatch[1]) : currentY;
      const newZ = zMatch ? parseFloat(zMatch[1]) : currentZ;

      if (xMatch || yMatch || zMatch) {
        const distance = Math.sqrt(
          Math.pow(newX - currentX, 2) +
          Math.pow(newY - currentY, 2) +
          Math.pow(newZ - currentZ, 2)
        );

        const moveTime = currentMode === 'G0' ? distance / rapidFeed : distance / currentFeed;
        totalTime += moveTime;
        if (currentMode === 'G0') rapidTime += moveTime;
        else cuttingTime += moveTime;

        moves.push({
          from: { x: currentX, y: currentY, z: currentZ },
          to: { x: newX, y: newY, z: newZ },
          distance,
          mode: currentMode,
          feed: currentMode === 'G0' ? rapidFeed : currentFeed,
          time: moveTime
        });

        currentX = newX;
        currentY = newY;
        currentZ = newZ;
      }
    }

    return {
      totalTimeMin: totalTime,
      totalTimeSec: totalTime * 60,
      rapidTimeMin: rapidTime,
      rapidTimeSec: rapidTime * 60,
      cuttingTimeMin: cuttingTime,
      cuttingTimeSec: cuttingTime * 60,
      totalMoves: moves.length,
      moves
    };
  }

  getTimeReport(result: NcTimeResult): string {
    const sep = '='.repeat(60);
    return `${sep}\nNC KOD SURE ANALIZI\n${sep}\n\n` +
      `Toplam Sure:\n  ${result.totalTimeMin.toFixed(3)} dakika\n  ${result.totalTimeSec.toFixed(2)} saniye\n\n` +
      `Hizli Hareket (G0) Suresi:\n  ${result.rapidTimeMin.toFixed(3)} dakika\n  ${result.rapidTimeSec.toFixed(2)} saniye\n\n` +
      `Kesme (G1) Suresi:\n  ${result.cuttingTimeMin.toFixed(3)} dakika\n  ${result.cuttingTimeSec.toFixed(2)} saniye\n\n` +
      `Toplam Hareket Sayisi: ${result.totalMoves}\n${sep}`;
  }

  // Merge Logic
  mergePosts() {
    if (!this.files.length) {
      this.clearMerge();
      return;
    }

    this.mergeInProgress = true;
    this.mergeProgress = 0;
    this.mergeStatus = 'Birlestirme baslatiliyor...';
    this.degisti();

    setTimeout(() => scrollTo({ left: 0, top: document.body.scrollHeight, behavior: 'smooth' }), 500);

    setTimeout(() => this.executeMerge(), 200);
  }

  updateCustomZLast(index: number, value: string) {
    const numValue = parseFloat(value);
    if (!isNaN(numValue)) {
      this.files[index].customZLast = numValue;
    } else {
      this.files[index].customZLast = null;
    }
    this.clearMerge();
    this.degisti();
  }

  getDisplayZLast(file: PostFile): number | null {
    return file.customZLast !== undefined && file.customZLast !== null
      ? file.customZLast
      : file.zLast;
  }

  /**
   * Tehlikeli Z bandi kontrolu:
   * Dosyanin en derin noktasi (zMin) +3 ile -70 arasinda ise true doner.
   * Sadece Zmin degeri arayuzde pulse animasyonu ile vurgulanir.
   */
  zPulseVar(file: PostFile): boolean {
    if (file.zMin === null) return false;
    return file.zMin <= 2.5 && file.zMin >= -70;
  }

  baglamaYonu(file: PostFile): "Uzun Taraf X" | "Uzun Taraf Y" | "Kare" {
    // if (file.zMin === null) return false;
    let xBoy = Math.abs(file.xMax) + Math.abs(file.xMin);
    let yBoy = Math.abs(file.yMax) + Math.abs(file.yMin);
    if (xBoy == yBoy) {
      return "Kare";
    }
    if (xBoy > yBoy) {
      return "Uzun Taraf X";
    } else if (yBoy > xBoy) {
      return "Uzun Taraf Y";
    }

  }

  OnIsl(file: PostFile): boolean {
    return (file.zMin + file.zMax) > 3;
  }

  enBuyukKutuk(): number {
    let enBuyuk = 0;

    for (const f of this.files) {
      const deger = f?.kutukZ ?? 0;
      if (deger > enBuyuk) {
        enBuyuk = deger;
      }
    }

    return enBuyuk;
  }

  tahminiKutukZ(file: PostFile): number {
    // if (file.zMin === null) return false;
    let zBoy = Math.abs(file.zMin) + Math.abs(file.zLast);

    if (file.zMin > 0) {
      // zBoy+=30;
    }

    zBoy = Math.floor(zBoy / 50) * 50;

    return zBoy;
  }

  /** On/Arka islemeyi elle degistirir; orijinal hesap degerini saklar. */
  onMuDegistir(index: number): void {
    const file = this.files[index];
    if (file.onMuOrijinal === undefined || file.onMuOrijinal === null) {
      file.onMuOrijinal = file.onMu ?? null;
    }
    file.onMu = !file.onMu;
    if (file.onMuOrijinal === file.onMu) {
      file.onMuOrijinal = null;
    }
    this.files = [...this.files];
    this.clearMerge();
  }

  gecmisler: GecmisKaydi[] = [];

  async gecmisYukle(): Promise<void> {
    try {
      this.gecmisler = await gecmisDeposu.listele();
    } catch {
      this.gecmisler = [];
    }
    this.degisti();
  }

  /** Basarili yazim/indirme sonrasi uretim kaydi olusturur; icerigin SHA-256 parmak izini saklar. */
  async gecmisKaydet(hedef: string): Promise<void> {
    if (!this.mergedResult) return;
    try {
      const tampon = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(this.mergedResult));
      const parmakIzi = Array.from(new Uint8Array(tampon)).map(b => b.toString(16).padStart(2, '0')).join('');
      let dk = 0;
      let enBuyukKutuk = 0;
      const dosyalar = this.files.map(f => {
        dk += this.calculateNcTime(f.cleanedContent, 15000, 10000).totalTimeMin;
        enBuyukKutuk = Math.max(enBuyukKutuk, f.kutukZ ?? 0);
        return {
          name: f.name,
          kaynakYol: f.kaynakYol ?? '',
          workOffset: f.workOffset,
          kutukZ: f.kutukZ ?? null,
          baglamaYonu: f.baglamaYonu ?? null,
          onMu: f.onMu ?? null,
          takimSatiri: f.takimSatiri ?? null,
          dosya: { ...f },
        };
      });
      await gecmisDeposu.ekle({
        id: this.makeId(),
        tarih: new Date().toISOString(),
        programNo: this.programNumber,
        programIsmi: this.programIsmi,
        hedef,
        dk,
        enBuyukKutuk,
        parmakIzi,
        birlesikIcerik: this.mergedResult,
        dosyalar,
      });
      await this.gecmisYukle();
    } catch (hata) {
      console.warn('Geçmiş kaydedilemedi', hata);
    }
  }

  /** Gecmis kaydindaki dosyalari ve birlestirilmis ciktiyi aynen geri yukler. */
  gecmisGeriYukle(k: GecmisKaydi): void {
    if (!k.birlesikIcerik || !k.dosyalar.every(d => d.dosya)) {
      window.alert('Bu kayit eski surumde olusturuldu, geri yuklenemez.');
      return;
    }
    this.files = k.dosyalar.map(d => ({ ...d.dosya }));
    this.programNumber = k.programNo;
    this.programIsmi = k.programIsmi;
    this.aktifSeansId = '';
    this.mergedResult = k.birlesikIcerik;
    this.resetMergedBounds();
    for (const f of this.files) this.mergeBoundsFromFile(f);
    this.degisti();
  }

  /** Gecmis kaydini onay alarak siler. */
  async gecmisSil(k: GecmisKaydi): Promise<void> {
    if (!window.confirm(`O${k.programNo} kaydı geçmişten silinecek. Devam edilsin mi?`)) return;
    try {
      await gecmisDeposu.sil(k.id);
      if (this.acikGecmisId === k.id) this.acikGecmisId = null;
    } catch (hata) {
      console.warn('Geçmiş silinemedi', hata);
    }
    await this.gecmisYukle();
  }

  acikGecmisId: string | null = null;

  gecmisAcKapat(id: string): void {
    this.acikGecmisId = this.acikGecmisId === id ? null : id;
    this.degisti();
  }

  gecmisArama = '';

  /** Program no, program adi, dosya adi veya kaynak klasor yoluna gore filtreler. */
  gecmisFiltreli(): GecmisKaydi[] {
    const q = this.gecmisArama.trim().toLocaleLowerCase('tr-TR');
    if (!q) return this.gecmisler;
    return this.gecmisler.filter(k =>
      String(k.programNo).includes(q) ||
      k.programIsmi.toLocaleLowerCase('tr-TR').includes(q) ||
      k.dosyalar.some(d =>
        d.name.toLocaleLowerCase('tr-TR').includes(q) ||
        d.kaynakYol.toLocaleLowerCase('tr-TR').includes(q))
    );
  }

  takimDuzenlenen: number | null = null;
  takimTaslak = '';

  /** Takim satiri satir ici duzenlemeye alinir. */
  takimDuzenlemeBaslat(index: number): void {
    this.takimDuzenlenen = index;
    this.takimTaslak = this.files[index].takimSatiri ?? '';
    this.degisti();
    setTimeout(() => (document.getElementById(`takim-${index}`) as HTMLInputElement | null)?.select(), 0);
  }

  takimDuzenlemeIptal(): void {
    this.takimDuzenlenen = null;
    this.degisti();
  }

  /** Satir ici girilen takim bilgisini uygular; orijinal degeri saklar ve takim capini gunceller. */
  takimDuzenlemeBitir(index: number, girdi: string): void {
    if (this.takimDuzenlenen !== index) return;
    this.takimDuzenlenen = null;
    const file = this.files[index];
    const yeni = girdi.trim();
    if (yeni === (file.takimSatiri ?? '').trim()) {
      this.degisti();
      return;
    }
    if (file.takimSatiriOrijinal === undefined || file.takimSatiriOrijinal === null) {
      file.takimSatiriOrijinal = file.takimSatiri ?? null;
    }
    file.takimSatiri = yeni;
    file.takimCapi = this.takimCapiAl(yeni);
    if (file.takimSatiriOrijinal === yeni) {
      file.takimSatiriOrijinal = null;
    }
    this.files = [...this.files];
    this.clearMerge();
  }

  kutukDuzenlenen: number | null = null;
  kutukTaslak = '';

  /** Kutuk olcusu satir ici duzenlemeye alinir. */
  kutukDuzenlemeBaslat(index: number): void {
    this.kutukDuzenlenen = index;
    this.kutukTaslak = String(this.files[index].kutukZ ?? '');
    this.degisti();
    setTimeout(() => (document.getElementById(`kutuk-${index}`) as HTMLInputElement | null)?.select(), 0);
  }

  kutukDuzenlemeIptal(): void {
    this.kutukDuzenlenen = null;
    this.degisti();
  }

  /** Satir ici girilen kutuk olcusunu uygular; orijinal hesap degerini saklar. */
  kutukDuzenlemeBitir(index: number, girdi: string): void {
    if (this.kutukDuzenlenen !== index) return;
    this.kutukDuzenlenen = null;
    const file = this.files[index];
    const deger = parseFloat(girdi.replace(',', '.'));
    if (isNaN(deger)) {
      this.degisti();
      return;
    }
    if (file.kutukZOrijinal === undefined || file.kutukZOrijinal === null) {
      file.kutukZOrijinal = file.kutukZ ?? null;
    }
    file.kutukZ = deger;
    if (file.kutukZOrijinal === deger) {
      file.kutukZOrijinal = null;
    }
    this.files = [...this.files];
    this.clearMerge();
  }

  /** Uzun taraf yonunu sirayla degistirir (X -> Y -> Kare); orijinal hesap degerini saklar. */
  baglamaYonuDegistir(index: number): void {
    const file = this.files[index];
    const sira = ['Uzun Taraf X', 'Uzun Taraf Y', 'Kare'];
    if (!file.baglamaYonuOrijinal) {
      file.baglamaYonuOrijinal = file.baglamaYonu;
    }
    const siradaki = sira[(sira.indexOf(file.baglamaYonu ?? '') + 1) % sira.length];
    file.baglamaYonu = siradaki;
    if (file.baglamaYonuOrijinal === siradaki) {
      file.baglamaYonuOrijinal = null;
    }
    this.files = [...this.files];
    this.clearMerge();
  }

  /** Dosyalara sirayla benzersiz work offset atar (G54, G55, ...). 12'den fazla dosyada offsetler tekrar eder. */
  otomatikOffsetAta(): void {
    this.files.forEach((f, i) => {
      f.workOffset = this.availableOffsets[i % this.availableOffsets.length];
    });
    this.files = [...this.files];
    this.clearMerge();
  }

  gecisleriAyarla(): void {
    let enBuyukKutuk = this.enBuyukKutuk()

    for (let index = 0; index < this.files.length; index++) {
      const element = this.files[index];

      if (element.onMu) {
        element.customZLast = enBuyukKutuk + 30;
      } else if (element.onMu == false) {
        let gecis = (enBuyukKutuk - element.kutukZ) + 30
        element.customZLast = gecis;
      }
    }
    this.CD.detectChanges();
  }


  private executeMerge() {
    this.mergeProgress = 20;
    this.mergeStatus = 'Header yaziliyor...';
    this.degisti();

    setTimeout(() => {
      this.mergeProgress = 40;
      this.mergeStatus = `Dosyalar isleniyor (0/${this.files.length})...`;
      this.degisti();

      const lines: string[] = [
        `O${this.programNumber || 1000}`,
        this.headerLines.join(' ')
      ];

      let toplamDakika = 0;

      for (const element of this.files) {
        if (this.basliklariYaz) {
          const timeResult = this.calculateNcTime(element.cleanedContent, 15000, 10000);
          const satir = element.cleanedContent.split('\n').length;

          const sonZti = element.customZLast !== undefined && element.customZLast !== null
            ? element.customZLast
            : element.zLast;

          lines.push(`//( ${element.workOffset} : ${element.name.replace('.NC','')} | SATIR:${satir} | DK:${timeResult.totalTimeMin.toFixed(3)} )`);
          lines.push(`//( X-:${this.formatCncNumber(element.xMin)} X+:${this.formatCncNumber(element.xMax)} Y-:${this.formatCncNumber(element.yMin)} Y+:${this.formatCncNumber(element.yMax)} Z-:${this.formatCncNumber(element.zMin)} Z+:${this.formatCncNumber(element.zMax)} )`);
          if (this.zPulseVar(element))
            lines.push(`//( DIKKAT ARKA ISLEM DEGIL ISE ZTE GIREBILIR )`);
          lines.push(`//( GECIS: ${this.formatCncNumber(sonZti)} | TKM: ${element.takimSatiri} | UZ: ${element.baglamaYonu.replace('Uzun Taraf ','')} | ISL: ${element.onMu==null ? '?':element.onMu ? 'ON':'ARKA' } | KTK: ${element.kutukZ} )`);
          lines.push(``);
          toplamDakika += timeResult.totalTimeMin;
        }
      }

      const totalSatir = this.files.reduce((sum, e) => sum + (e.cleanedContent?.split('\n').length || 0), 0);
      lines.push(`// ( -- FIKOYT SISTEM - YENI CAG | SATIR:${totalSatir} | DK:${toplamDakika.toFixed(3)} -- )`);

      // Limit kontrol gezisi
      if (this.limitKontrolYap) {
        lines.push('');
        lines.push('//( ========== LIMIT KONTROL GEZISI ========== )');
        lines.push('//( TEZGAH TUM DOSYALARIN LIMITLERINI GEZECEK )');
        lines.push('//( HER NOKTADA M0 ILE DURACAK - KONTROL ET )');
        lines.push('');

        for (const element of this.files) {
          const sonZti = element.customZLast !== undefined && element.customZLast !== null
            ? element.customZLast
            : element.zLast;

          lines.push(`//( --- ${element.name} LIMIT KONTROLU --- )`);
          lines.push(element.workOffset);
          lines.push(`//( ${element.workOffset} : ${element.name} )`);

          lines.push(`G0 X0. Y0. M0 //( MERKEZ - DOGRUMU? CYCLE START )`);
          if (sonZti !== null) {
            lines.push(`G0 Z${this.formatCncNumber(sonZti)} //( GUVENLI YUKSEKLIK )`);
          }

          if (element.xMin !== null) {
            lines.push(`G0 X${this.formatCncNumber(element.xMin)} M0 //( X- LIMIT - SIKINTI YOKSA CYCLE START )`);
          }
          if (element.xMax !== null) {
            lines.push(`G0 X${this.formatCncNumber(element.xMax)} M0 //( X+ LIMIT - SIKINTI YOKSA CYCLE START )`);
          }

          if (element.yMin !== null) {
            lines.push(`G0 Y${this.formatCncNumber(element.yMin)} M0 //( Y- LIMIT - SIKINTI YOKSA CYCLE START )`);
          }
          if (element.yMax !== null) {
            lines.push(`G0 Y${this.formatCncNumber(element.yMax)} M0 //( Y+ LIMIT - SIKINTI YOKSA CYCLE START )`);
          }

          lines.push(`//( Z-:${this.formatCncNumber(element.zMin)} Z+:${this.formatCncNumber(element.zMax)} )`);
          lines.push(`//( SON GECIS ZTI:${this.formatCncNumber(sonZti)} )`);
          lines.push('//( TAKIMI VE ZTI KONTROL ET )');

          if (sonZti !== null) {
            lines.push(`G0 Z${this.formatCncNumber(sonZti)} M0 //( ZTI - DIGER KALIBA GECIS BURADAN OLACAK )`);
          }
          lines.push('');
        }

        lines.push('//( ========== LIMIT KONTROL BITTI ========== )');
        lines.push('G0 G90 G53 Z-1.');
        lines.push('M0 //( SIMDI GERCEK ISLEM BASLAYACAK )');
        lines.push('');
      }

      this.resetMergedBounds();

      let processedFiles = 0;
      const totalFiles = this.files.length;

      for (const f of this.files) {
        processedFiles++;
        this.mergeProgress = 40 + Math.floor((processedFiles / totalFiles) * 50);
        this.mergeStatus = `Dosyalar isleniyor (${processedFiles}/${totalFiles})...`;
        this.degisti();

        lines.push(`//(--- BASLANGIC: ${f.name} | Offset: ${f.workOffset} ---)`);
        if (f.workOffset) lines.push(f.workOffset);

        if (f.cleanedContent.trim()) {
          const contentLines = f.cleanedContent.trim().split('\n');
          const cleanedLines = this.removeProgramEnd(contentLines);

          if (f.customZLast !== undefined && f.customZLast !== null && f.customZLast !== f.zLast) {
            for (let i = cleanedLines.length - 1; i >= 0; i--) {
              const line = cleanedLines[i];
              if (/Z-?\d+\.?\d*/i.test(line)) {
                cleanedLines[i] = line.replace(/Z-?\d+\.?\d*/i, `Z${this.formatCncNumber(f.customZLast)}`);
                break;
              }
            }
          }

          lines.push(cleanedLines.join('\n'));
        }

        lines.push(`//(--- BITIS: ${f.name} ---)`);

        this.mergeBoundsFromFile(f);
      }

      this.mergeProgress = 95;
      this.mergeStatus = 'Son kontroller yapiliyor...';
      this.degisti();

      lines.push('M5', 'M30', 'M2');
      this.mergedResult = lines.join('\n');

      setTimeout(() => {
        this.mergeProgress = 100;
        this.mergeStatus = 'Tamamlandi!';
        this.degisti();

        setTimeout(() => {
          this.mergeInProgress = false;
          this.programIsmi = this.files.length === 1
            ? (this.files[0]?.name?.replace(/\.[^/.]+$/, '') || 'BIRLESIK')
            : 'BIRLESIK';
          this.degisti();
          scrollTo({ left: 0, top: document.body.scrollHeight, behavior: 'smooth' });
        }, 800);
      }, 200);
    }, 300);
  }

  downloadMerged() {
    if (!this.mergedResult) return;
    const blob = new Blob([this.mergedResult], { type: 'text/plain' });
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${this.programIsmi || "BIRLESIK"}.NC`;
    document.body.appendChild(a);
    a.click();
    a.remove();
    window.URL.revokeObjectURL(url);
    void this.gecmisKaydet('İndirildi');
  }

  clearAll() {
    this.files = [];
    this.mergedResult = '';
    this.programNumber = 1000;
    this.resetMergedBounds();
  }

  // Formatting Helpers
  formatFileSize(bytes: number): string {
    return `${(bytes / 1024).toFixed(1)} KB`;
  }

  formatCoordinate(value: number | null): string {
    return value !== null ? value.toFixed(3) : 'N/A';
  }

  formatCncNumber(value: number | null): string {
    if (value === null) return 'N/A';
    const str = value.toString();
    return str.includes('.') ? str : str + '.';
  }

  formatDate(dateStr: string): string {
    if (!dateStr) return '';
    try {
      return new Date(dateStr).toLocaleString('tr-TR');
    } catch {
      return dateStr;
    }
  }

  getBoundsInfo(file: PostFile): string {
    const parts: string[] = [];
    if (file.xMin !== null && file.xMax !== null) {
      parts.push(`X: ${this.formatCoordinate(file.xMin)} / ${this.formatCoordinate(file.xMax)}`);
    }
    if (file.yMin !== null && file.yMax !== null) {
      parts.push(`Y: ${this.formatCoordinate(file.yMin)} / ${this.formatCoordinate(file.yMax)}`);
    }
    if (file.zMin !== null && file.zMax !== null) {
      parts.push(`Z: ${this.formatCoordinate(file.zMin)} / ${this.formatCoordinate(file.zMax)}`);
    }

    const displayZLast = file.customZLast !== undefined && file.customZLast !== null
      ? file.customZLast
      : file.zLast;

    if (displayZLast !== null) {
      parts.push(`Son Z: ${this.formatCoordinate(displayZLast)}`);
    }
    return parts.length > 0 ? parts.join(' | ') : 'Koordinat bulunamadi';
  }

  getMergedBoundsInfo(): string {
    const parts: string[] = [];
    const b = this.mergedBounds;
    if (b.xMin !== null && b.xMax !== null) {
      parts.push(`X: ${this.formatCoordinate(b.xMin)} / ${this.formatCoordinate(b.xMax)}`);
    }
    if (b.yMin !== null && b.yMax !== null) {
      parts.push(`Y: ${this.formatCoordinate(b.yMin)} / ${this.formatCoordinate(b.yMax)}`);
    }
    if (b.zMin !== null && b.zMax !== null) {
      parts.push(`Z: ${this.formatCoordinate(b.zMin)} / ${this.formatCoordinate(b.zMax)}`);
    }
    if (b.zLast !== null) {
      parts.push(`Son Z: ${this.formatCoordinate(b.zLast)}`);
    }
    return parts.length > 0 ? parts.join(' | ') : 'Henuz birlestirme yapilmadi';
  }

  // Bounds Helpers
  private createEmptyBounds(): Bounds {
    return {
      xMin: null, xMax: null,
      yMin: null, yMax: null,
      zMin: null, zMax: null,
      zLast: null
    };
  }

  private resetMergedBounds() {
    this.mergedBounds = this.createEmptyBounds();
  }

  private mergeBoundsFromFile(file: PostFile) {
    const b = this.mergedBounds;
    if (file.xMin !== null) b.xMin = b.xMin === null ? file.xMin : Math.min(b.xMin, file.xMin);
    if (file.xMax !== null) b.xMax = b.xMax === null ? file.xMax : Math.max(b.xMax, file.xMax);
    if (file.yMin !== null) b.yMin = b.yMin === null ? file.yMin : Math.min(b.yMin, file.yMin);
    if (file.yMax !== null) b.yMax = b.yMax === null ? file.yMax : Math.max(b.yMax, file.yMax);
    if (file.zMin !== null) b.zMin = b.zMin === null ? file.zMin : Math.min(b.zMin, file.zMin);
    if (file.zMax !== null) b.zMax = b.zMax === null ? file.zMax : Math.max(b.zMax, file.zMax);
    if (file.zLast !== null) b.zLast = file.zLast;
  }

  // Duplicate Offset Detection
  getDuplicateOffsets(): { offset: string; files: string[] }[] {
    const offsetMap = new Map<string, string[]>();
    for (const file of this.files) {
      const existing = offsetMap.get(file.workOffset) || [];
      existing.push(file.name);
      offsetMap.set(file.workOffset, existing);
    }

    const duplicates: { offset: string; files: string[] }[] = [];
    for (const [offset, files] of offsetMap) {
      if (files.length > 1) {
        duplicates.push({ offset, files });
      }
    }
    return duplicates;
  }

  hasDuplicateOffsets(): boolean {
    return this.getDuplicateOffsets().length > 0;
  }

  /** Aynı work offset'i kullanan dosya sayisi */
  offsetKullanim(offset: string): number {
    return this.files.filter(f => f.workOffset === offset).length;
  }

  /**
   * Duplicate olan work offset'ler icin ayi edici renk doner.
   * Ayni offset'i kullanan butun dosyalar ayni rengi alir.
   * Offset tek basina kullaniliyorsa null doner (varsayilan stil).
   */
  private readonly offsetRenkPaleti = [
    '#e74c3c', // kirmizi
    '#9b59b6', // mor
    '#e67e22', // turuncu
    '#16a085', // turkuaz
    '#c0392b', // koyu kirmizi
    '#2980b9', // mavi
    '#d35400', // kahverengi-turuncu
    '#8e44ad', // koyu mor
    '#f39c12', // sari
    '#27ae60', // yesil
    '#2c3e50', // lacivert
    '#7f8c8d'  // gri
  ];

  offsetRengi(offset: string): string | null {
    if (this.offsetKullanim(offset) <= 1) return null;
    const duplicates = this.getDuplicateOffsets().map(d => d.offset);
    const index = duplicates.indexOf(offset);
    if (index === -1) return null;
    return this.offsetRenkPaleti[index % this.offsetRenkPaleti.length];
  }

  // Change Detection
  degisti() {
    this.CD.markForCheck();
  }

  // Pagination
  async onPageChange(page: number) {
    this.pageIndex = page;
    this.loadRecentFiles();
  }

  async onPageSizeChange(size: number) {
    this.loadRecentFiles();
  }

  getFirstNLines(str: string, n: number): string {
    let count = 0;
    let lastIndex = 0;
    while (count < n) {
      const nextIndex = str.indexOf('\n', lastIndex);
      if (nextIndex === -1) return str;
      lastIndex = nextIndex + 1;
      count++;
    }
    return str.slice(0, lastIndex);
  }

  // Seans kaydedici
  seanslar: KayitliSeans[] = [];

  aktifSeansId = '';
  seansAdi = '';
  seansMesaj = '';

  async seanslariYukle() {
    try {
      this.seanslar = await seansDeposu.listele();
    } catch {
      this.seansMesaj = 'Kayıtlar okunamadı';
    }
    this.degisti();
  }

  async seansKaydet(yeniKayit: boolean) {
    const ad = this.seansAdi.trim();
    if (!ad || !this.files.length) return;
    const id = !yeniKayit && this.aktifSeansId ? this.aktifSeansId : this.makeId();
    try {
      await seansDeposu.kaydet({
        id,
        ad,
        tarih: new Date().toISOString(),
        dosyalar: this.files.map(f => ({ ...f })),
      });
      this.aktifSeansId = id;
      this.seansMesaj = `"${ad}" kaydedildi`;
    } catch {
      this.seansMesaj = 'Kayıt yapılamadı';
    }
    await this.seanslariYukle();
  }

  seansAc(s: KayitliSeans) {
    this.files = s.dosyalar.map(f => ({ ...f }));
    this.aktifSeansId = s.id;
    this.seansAdi = s.ad;
    this.clearMerge();
    this.seansMesaj = `"${s.ad}" açıldı`;
    this.degisti();
  }

  private readonly seansOzetOnbellek = new Map<string, string>();

  /** Seans icin toplam sure (dk) ve en buyuk kutuk ozeti; ayni kayit icin sonucu onbellekte tutar. */
  seansOzeti(s: KayitliSeans): string {
    const anahtar = `${s.id}|${s.tarih}`;
    const onbellekte = this.seansOzetOnbellek.get(anahtar);
    if (onbellekte) return onbellekte;

    let toplamDakika = 0;
    let enBuyukKutuk = 0;
    for (const f of s.dosyalar as PostFile[]) {
      toplamDakika += this.calculateNcTime(f.cleanedContent, 15000, 10000).totalTimeMin;
      enBuyukKutuk = Math.max(enBuyukKutuk, f.kutukZ ?? 0);
    }

    const ozet = `${toplamDakika.toFixed(1)} dk · kütük ${enBuyukKutuk}`;
    this.seansOzetOnbellek.set(anahtar, ozet);
    return ozet;
  }

  seansYeni() {
    this.files = [];
    this.aktifSeansId = '';
    this.seansAdi = '';
    this.seansMesaj = '';
    this.clearMerge();
  }

  async seansSil(s: KayitliSeans) {
    if (!window.confirm(`"${s.ad}" seansı silinecek. Devam edilsin mi?`)) return;
    try {
      await seansDeposu.sil(s.id);
      if (this.aktifSeansId === s.id) this.aktifSeansId = '';
      this.seansMesaj = `"${s.ad}" silindi`;
    } catch {
      this.seansMesaj = 'Silme başarısız';
    }
    await this.seanslariYukle();
  }

  // Post Operations
  async postKopyala(dosya: any) {
    try {
      await this.everything.postSil(dosya.name);
    } catch (error) {
      console.warn("Silme basarisiz (dosya yok olabilir):", error);
    }

    await new Promise(resolve => setTimeout(resolve, 1000));

    const cleanPath = dosya.url.replace(/\\/g, '/').replace(/^[A-Za-z]:/, '').replace(/^\/+/, '');
    await this.everything.postKopyala(cleanPath, dosya.name);
  }
}
