// fikoyt-eklenti-everything: Everything dosya aramasi, klasor acma ve dosya indirme eklentisi (bagimsiz surec).
// Protokol (bridge eklenti_calistir.go ile ayni): stdin'e tek satir JSON, stdout'a tek satir JSON.
//
//	girdi:  {"islem":"ara","veri":{"sorgu":"metin","sayfa":1,"boyut":50,"siralama":"ad","artan":true,"case":false,"wholeword":false,"path":false,"regex":false}}
//	girdi:  {"islem":"klasor-ac","veri":{"yol":"D:\\klasor\\dosya.pdf"}}  (dosyanin klasorunu Gezgin'de acar)
//	girdi:  {"islem":"dosya-parca","veri":{"yol":"...","baslangic":0,"uzunluk":262144}}  (izinli dosyanin parcasi)
//	cikti:  {"tamam":true,"veri":...} ya da {"tamam":false,"hata":"..."}
//
// Everything yalnizca yerel HTTP sunucusuna (127.0.0.1:8888) gider.
package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	maksSorgu     = 400
	maksSonuc     = 200 // sayfa boyutu ust siniri; cikti siniri (1 MB) icinde kalir
	maksYanitBoyu = 512 * 1024
)

// everythingAdres: test icin degistirilebilir; varsayilan yerel adres.
var everythingAdres = "http://127.0.0.1:8888/"

// Everything HTTP Basic kimlik dogrulama (bos ise kimlik gonderilmez). Parola asla cikti/log'a yazilmaz.
var (
	everythingKullanici string
	everythingParola    string
)

// hostDesen: yalnizca ana bilgisayar adi ya da IPv4 (sema, yol ya da port icermez).
var hostDesen = regexp.MustCompile(`^[A-Za-z0-9.-]{1,253}$`)

// windowsYolDesen: yalnizca mutlak Windows yolu (ornek: D:\klasor\dosya.pdf). Goreli yol ve UNC kabul edilmez.
var windowsYolDesen = regexp.MustCompile(`^[A-Za-z]:\\[^<>"|?*\x00-\x1f]+$`)

// siralamaAlanlari: web'den gelen siralama alanlarinin Everything sort adlari.
// Everything yalnizca name, path, date_modified ve size ile siralar.
var siralamaAlanlari = map[string]string{
	"ad":    "name",
	"konum": "path",
	"tur":   "name",
	"boyut": "size",
	"tarih": "date_modified",
}

var istemci = &http.Client{Timeout: 10 * time.Second}

type girdi struct {
	ID    string `json:"id,omitempty"`
	Islem string `json:"islem"`
	Veri  struct {
		Sorgu     string `json:"sorgu"`
		Yol       string `json:"yol"`
		Baslangic int64  `json:"baslangic"`
		Uzunluk   int64  `json:"uzunluk"`
		Sayfa     int    `json:"sayfa"`
		Boyut     int    `json:"boyut"`
		Siralama  string `json:"siralama"`
		Artan     bool   `json:"artan"`
		Case      bool   `json:"case"`
		WholeWord bool   `json:"wholeword"`
		FullPath  bool   `json:"path"`
		Regex     bool   `json:"regex"`
	} `json:"veri"`
	// Ayar: bu PC'de kullanicinin kaydettigi ayar. port: Everything HTTP sunucusu (varsayilan 8888).
	// host bos ise 127.0.0.1; kullanici/parola bossa kimlik dogrulama yok.
	Ayar struct {
		Port            int    `json:"port"`
		Host            string `json:"host"`
		Kullanici       string `json:"kullanici"`
		Parola          string `json:"parola"`
		IndirmeSuresiDk int    `json:"indirmeSuresiDk"`
		IndirmeMaksMb   int    `json:"indirmeMaksMb"`
	} `json:"ayar"`
}

type cikti struct {
	ID    string          `json:"id,omitempty"`
	Tamam bool            `json:"tamam"`
	Veri  json.RawMessage `json:"veri,omitempty"`
	Hata  string          `json:"hata,omitempty"`
}

// aramaSecenek: Everything arama bayraklari (buyuk-kucuk harf, tam kelime, tam yol, duzenli ifade).
type aramaSecenek struct {
	Case, WholeWord, FullPath, Regex bool
}

var (
	errIslem      = errors.New("bilinmeyen islem")
	errSorgu      = errors.New("sorgu gerekli ve en fazla 400 karakter")
	errUlasilamaz = errors.New("Everything HTTP sunucusuna ulasilamadi")
	errKimlik     = errors.New("Everything kullanıcı adı veya parola hatalı")
)

// ara: Everything'de sayfalanmis arama yapar. Toplam sayi (totalResults) Everything yanitinda gelir.
func ara(sorgu string, sayfa, boyut int, siralama string, artan bool, secenek aramaSecenek) (json.RawMessage, error) {
	if boyut < 1 || boyut > maksSonuc {
		boyut = 50
	}
	if sayfa < 1 || sayfa > 100000 {
		sayfa = 1
	}
	hedef, err := url.Parse(everythingAdres)
	if err != nil {
		return nil, err
	}
	q := url.Values{}
	q.Set("search", sorgu)
	q.Set("json", "1")
	q.Set("count", strconv.Itoa(boyut))
	q.Set("offset", strconv.Itoa((sayfa-1)*boyut))
	// *_column: sonuclarda yol, boyut ve tarih alanlarini ister.
	q.Set("path_column", "1")
	q.Set("size_column", "1")
	q.Set("date_modified_column", "1")
	if secenek.Case {
		q.Set("case", "1")
	}
	if secenek.WholeWord {
		q.Set("wholeword", "1")
	}
	if secenek.FullPath {
		q.Set("path", "1")
	}
	if secenek.Regex {
		q.Set("regex", "1")
	}
	if alan, ok := siralamaAlanlari[siralama]; ok {
		q.Set("sort", alan)
		if artan {
			q.Set("ascending", "1")
		} else {
			q.Set("ascending", "0")
		}
	}
	hedef.RawQuery = q.Encode()

	istek, err := http.NewRequest(http.MethodGet, hedef.String(), nil)
	if err != nil {
		return nil, errUlasilamaz
	}
	if everythingKullanici != "" || everythingParola != "" {
		istek.SetBasicAuth(everythingKullanici, everythingParola)
	}
	yanit, err := istemci.Do(istek)
	if err != nil {
		return nil, errUlasilamaz
	}
	defer yanit.Body.Close()
	if yanit.StatusCode == http.StatusUnauthorized {
		return nil, errKimlik
	}
	if yanit.StatusCode != http.StatusOK {
		return nil, errUlasilamaz
	}
	govde, err := io.ReadAll(io.LimitReader(yanit.Body, maksYanitBoyu))
	if err != nil {
		return nil, errUlasilamaz
	}
	if !json.Valid(govde) {
		return nil, errors.New("Everything gecersiz yanit dondu")
	}
	return govde, nil
}

// klasorAc: dosyanin bulundugu klasoru Gezgin'de acar ve dosyayi secili gosterir.
// Dosya calistirilmaz. Yol mutlak Windows yolu olmali ve var olan bir dosyaya ait olmali.
func klasorAc(yol string) cikti {
	yol = strings.TrimSpace(yol)
	if len(yol) > 1024 || !windowsYolDesen.MatchString(yol) || strings.Contains(yol, "..") {
		return cikti{Tamam: false, Hata: "geçersiz dosya yolu"}
	}
	bilgi, err := os.Stat(yol)
	if err != nil {
		return cikti{Tamam: false, Hata: "dosya bulunamadı"}
	}
	if bilgi.IsDir() {
		return cikti{Tamam: false, Hata: "klasör değil, dosya gerekli"}
	}
	if err := explorerAc(yol); err != nil {
		return cikti{Tamam: false, Hata: "klasör açılamadı"}
	}
	return cikti{Tamam: true}
}

func islemeAl(satir []byte) cikti {
	var g girdi
	if err := json.Unmarshal(satir, &g); err != nil {
		return cikti{Tamam: false, Hata: "gecersiz girdi"}
	}
	if g.Islem == "ping" {
		return cikti{Tamam: true}
	}
	if g.Islem == "klasor-ac" {
		return klasorAc(g.Veri.Yol)
	}
	if g.Islem == "dosya-parca" {
		return dosyaParca(g.Veri.Yol, g.Veri.Baslangic, g.Veri.Uzunluk, g.Ayar.IndirmeMaksMb)
	}
	if a := g.Ayar; a.Port != 0 || a.Host != "" || a.Kullanici != "" || a.Parola != "" {
		if a.Port != 0 && (a.Port < 1 || a.Port > 65535) {
			return cikti{Tamam: false, Hata: "gecersiz port"}
		}
		host := a.Host
		if host == "" {
			host = "127.0.0.1"
		}
		if !hostDesen.MatchString(host) {
			return cikti{Tamam: false, Hata: "gecersiz sunucu adresi"}
		}
		port := a.Port
		if port == 0 {
			port = 8888
		}
		everythingAdres = fmt.Sprintf("http://%s:%d/", host, port)
		everythingKullanici = a.Kullanici
		everythingParola = a.Parola
	}
	if g.Islem != "ara" {
		return cikti{Tamam: false, Hata: errIslem.Error()}
	}
	sorgu := strings.TrimSpace(g.Veri.Sorgu)
	if sorgu == "" || len(sorgu) > maksSorgu {
		return cikti{Tamam: false, Hata: errSorgu.Error()}
	}
	secenek := aramaSecenek{Case: g.Veri.Case, WholeWord: g.Veri.WholeWord, FullPath: g.Veri.FullPath, Regex: g.Veri.Regex}
	veri, err := ara(sorgu, g.Veri.Sayfa, g.Veri.Boyut, g.Veri.Siralama, g.Veri.Artan, secenek)
	if err != nil {
		return cikti{Tamam: false, Hata: err.Error()}
	}
	// Sonuclardaki dosyalar kisa sureli indirme iznine yazilir (dosya-parca bunu kontrol eder).
	izinKaydet(veri, g.Ayar.IndirmeSuresiDk)
	return cikti{Tamam: true, Veri: onizlemeZenginlestir(veri, satir)}
}

// onizlemeZenginlestir: kullanicinin ayarina gore sonuca kucuk onizleme ve dosya simgesi alanlari ekler.
func onizlemeZenginlestir(veri json.RawMessage, satir []byte) json.RawMessage {
	var h struct {
		Ayar json.RawMessage `json:"ayar"`
	}
	_ = json.Unmarshal(satir, &h)
	if kucuk := kucukAyarOku(h.Ayar); kucuk.Acik {
		veri = kucukOnizlemeleriEkle(veri, kucuk)
	}
	if simgeAcik(h.Ayar) {
		veri = simgeleriEkle(veri)
	}
	return veri
}

func main() {
	// Kalici mod: stdin'den satir satir istek okunur; her cevap istegin id'sini tasir.
	// Stdin kapaninca (bridge kapandi ya da durdurdu) surec sonlanir.
	tarayici := bufio.NewScanner(os.Stdin)
	tarayici.Buffer(make([]byte, 64*1024), 1024*1024)
	kodlayici := json.NewEncoder(os.Stdout)
	for tarayici.Scan() {
		satir := append([]byte(nil), tarayici.Bytes()...)
		var g girdi
		_ = json.Unmarshal(satir, &g)
		c := islemeAl(satir)
		c.ID = g.ID
		_ = kodlayici.Encode(c)
	}
}
