<?php
declare(strict_types=1);
namespace App\Support;

final class CommercialDocumentPdf
{
    public static function output(array $document, string $filename): never
    {
        $pdf=self::build($document);
        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="'.preg_replace('/[^A-Za-z0-9_.-]/','_',$filename).'"');
        header('Content-Length: '.strlen($pdf));
        echo $pdf;
        exit;
    }

    public static function build(array $d): string
    {
        $company=$d['company']??self::company((int)$d['company_id']);
        $customer=$d['customer']??self::customer((int)$d['customer_id']);
        $logo=self::logo($company);
        $lines=array_values($d['lines']??[]);
        $chunks=array_chunk($lines,18)?:[[]];
        $pages=[];
        foreach($chunks as$i=>$chunk)$pages[]=self::page($d,$company,$customer,$chunk,$i+1,count($chunks),$i===count($chunks)-1,$logo);
        return self::pdf($pages,$logo);
    }

    private static function company(int $id): array
    {
        $s=app()->db()->connection()->prepare('SELECT * FROM companies WHERE id=:id');$s->execute(['id'=>$id]);$company=$s->fetch()?:[];
        $b=app()->db()->connection()->prepare('SELECT * FROM branding_settings WHERE company_id=:id');$b->execute(['id'=>$id]);
        return array_merge($company,$b->fetch()?:[]);
    }

    private static function customer(int $id): array
    {
        $s=app()->db()->connection()->prepare('SELECT * FROM third_parties WHERE id=:id');$s->execute(['id'=>$id]);return$s->fetch()?:[];
    }

    private static function page(array$d,array$c,array$customer,array$lines,int$page,int$count,bool$last,?array$logo):string
    {
        $x=[];$navy=[0.04,0.08,0.28];$blue=[0.08,0.42,0.78];$gray=[0.93,0.94,0.95];$light=[0.97,0.98,0.99];
        $x[]=self::fill(0,0,595,842,1,1,1);
        for($i=0;$i<6;$i++){$y=790-$i*118;$x[]=self::strokeHex(-12,$y,42,.94,.95,.96);}
        $x[]=self::line(350,842,470,722,.86,.87,.89,2);$x[]=self::line(430,842,555,717,.86,.87,.89,2);$x[]=self::line(280,100,410,-30,.89,.90,.92,2);
        $short=trim((string)($c['app_short_name']??$c['trade_name']??$c['legal_name']??'GC PRO'));
        if($logo){$maxW=115;$maxH=62;$ratio=min($maxW/$logo['width'],$maxH/$logo['height']);$w=$logo['width']*$ratio;$h=$logo['height']*$ratio;$x[]=sprintf('q %.2F 0 0 %.2F 30 %.2F cm /Logo Do Q',$w,$h,800-$h);}
        else{$x[]=self::fill(30,745,72,55,$blue[0],$blue[1],$blue[2]);$x[]=self::text(66,774,20,self::initials($short),true,'C');$x[]=self::text(30,730,14,$short,true);}
        $title=strtoupper((string)$d['title']).' '.(string)$d['number'];
        $x[]=self::text(560,790,17,$title,true,'R',$navy);
        $x[]=self::text(560,770,9,'Date : '.self::date($d['date']??null),false,'R',$navy);
        if(!empty($d['due_date']))$x[]=self::text(560,757,9,'Échéance : '.self::date($d['due_date']),false,'R',$navy);
        $x[]=self::text(560,744,9,'Code client : '.($customer['code']??'-'),false,'R',$navy);

        $x[]=self::text(30,710,8,'ÉMETTEUR',true);$x[]=self::fill(30,602,245,100,$gray[0],$gray[1],$gray[2]);
        $x[]=self::text(42,680,11,$c['legal_name']??$short,true,'L',$navy);
        $x[]=self::text(42,663,9,trim(($c['address_line']??'').' '.($c['city']??'')));
        $x[]=self::text(42,648,9,'ICE : '.($c['ice']??'-').'   IF : '.($c['tax_id']??'-'));
        $x[]=self::text(42,633,9,'Tél : '.($c['phone']??'-').'   Email : '.($c['email']??'-'));
        $x[]=self::text(318,710,8,'ADRESSÉ À',true);$x[]=self::rect(318,602,247,100,.55,.57,.60);
        $clientName=trim((string)($customer['legal_name']?:(($customer['first_name']??'').' '.($customer['last_name']??''))));
        $x[]=self::text(330,680,11,$clientName?:'Client',true,'L',$navy);
        $x[]=self::text(330,663,9,trim(($customer['address_line']??'').' '.($customer['city']??'')));
        $x[]=self::text(330,648,9,'ICE : '.($customer['ice']??'-').'   IF : '.($customer['tax_id']??'-'));
        $x[]=self::text(330,633,9,'Tél : '.($customer['phone_primary']??'-'));
        $note=trim((string)($d['note']??''));if($note!==''){$x[]=self::rect(30,575,535,20,.65,.65,.65);$x[]=self::text(35,582,8,self::cut($note,112));}

        $top=555;$bottom=235;$x[]=self::fill(30,$top-20,535,20,$navy[0],$navy[1],$navy[2]);
        $isDelivery=!empty($d['delivery']);
        $columns=$isDelivery?[[30,305,'Désignation','L'],[335,65,'Commandé','R'],[400,70,'Livré','R'],[470,95,'Unité','R']]:[[30,285,'Désignation','L'],[315,75,'P.U. HT','R'],[390,55,'Qté','R'],[445,45,'TVA','R'],[490,75,'Total','R']];
        foreach($columns as[$cx,$cw,$label,$align])$x[]=self::text($align==='R'?$cx+$cw-5:$cx+5,$top-13,8,$label,true,$align,[1,1,1]);
        $x[]=self::rect(30,$bottom,535,$top-20-$bottom,.55,.57,.60);
        foreach(array_slice($columns,1)as$col)$x[]=self::line($col[0],$bottom,$col[0],$top-20,.65,.67,.70,.5);
        $y=$top-38;
        foreach($lines as$line){
            $designation=trim(($line['reference']??'').' - '.($line['designation']??''),' -');$x[]=self::text(35,$y,8,self::cut($designation,$isDelivery?64:58));
            if($isDelivery){$x[]=self::text(395,$y,8,self::qty($line['ordered']??0),false,'R');$x[]=self::text(465,$y,8,self::qty($line['quantity']??0),false,'R');$x[]=self::text(560,$y,8,(string)($line['unit']??''),false,'R');}
            else{$x[]=self::text(385,$y,8,self::money($line['unit_price']??0),false,'R');$x[]=self::text(440,$y,8,self::qty($line['quantity']??0),false,'R');$x[]=self::text(485,$y,8,self::qty($line['tax_rate']??20).'%',false,'R');$x[]=self::text(560,$y,8,self::money($line['total']??0),false,'R');}
            $y-=16;
        }
        if(!$lines)$x[]=self::text(297,440,10,'Aucune ligne',false,'C',[.45,.45,.45]);

        if($last&&!$isDelivery){
            $currency=(string)($d['currency']??'MAD');$x[]=self::text(35,215,8,'Conditions de règlement :',true);$x[]=self::text(155,215,8,(string)($d['payment_terms']??'À réception'));
            $labels=[['Total HT',$d['subtotal']??0],['Total TVA 20%',$d['tax_total']??0],['Total TTC',$d['total']??0]];$yy=215;
            foreach($labels as$i=>[$label,$value]){$x[]=self::fill(360,$yy-4,205,17,$i===2?.86:.96,$i===2?.87:.96,$i===2?.90:.96);$x[]=self::text(370,$yy+1,9,$label,$i===2);$x[]=self::text(558,$yy+1,9,self::money($value).' '.$currency,$i===2,'R',$navy);$yy-=18;}
            if(isset($d['amount_words']))$words=$d['amount_words'];else$words=AmountInWordsFr::money((float)($d['total']??0),$currency);
            $x[]=self::text(35,125,9,'ARRÊTÉ LE PRÉSENT DOCUMENT À LA SOMME DE :',true);$x[]=self::text(35,108,9,self::cut((string)$words,105));
        }elseif($last){$x[]=self::text(35,185,9,'Observations : '.self::cut((string)($d['note']??'-'),90));$x[]=self::text(385,160,9,'Réceptionnaire / Signature',true);$x[]=self::rect(380,85,185,65,.65,.65,.65);}

        $footer=trim((string)($c['footer_text']??''));if($footer==='')$footer='Siège social : '.trim(($c['address_line']??'').' '.($c['city']??''));
        $x[]=self::line(30,58,565,58,.82,.83,.85,.5);$x[]=self::text(297,44,7,self::cut($footer,125),true,'C');
        $legal='ICE: '.($c['ice']??'-').' - RC: '.($c['rc']??'-').' - Tél: '.($c['phone']??'-').' - Email: '.($c['email']??'-');
        $x[]=self::text(30,27,7,self::cut($legal,118));$x[]=self::text(565,27,7,$page.' / '.$count,false,'R');
        return implode("\n",$x);
    }

    private static function logo(array $company): ?array
    {
        $relative=trim((string)($company['logo_path']??''));
        if($relative==='')return null;
        $public=realpath(dirname(__DIR__,2).'/public');
        $path=realpath(dirname(__DIR__,2).'/public/'.ltrim(str_replace('\\','/',$relative),'/'));
        if(!$public||!$path||!str_starts_with($path,$public.DIRECTORY_SEPARATOR)||!is_file($path))return null;
        $info=@getimagesize($path);if(!$info)return null;
        $raw=@file_get_contents($path);if($raw===false)return null;
        if(function_exists('imagecreatefromstring')&&function_exists('imagejpeg')){
            $source=@imagecreatefromstring($raw);if(!$source)return null;
            $sourceW=imagesx($source);$sourceH=imagesy($source);$ratio=min(1,600/$sourceW,300/$sourceH);
            $width=max(1,(int)round($sourceW*$ratio));$height=max(1,(int)round($sourceH*$ratio));
            $canvas=imagecreatetruecolor($width,$height);$white=imagecolorallocate($canvas,255,255,255);imagefill($canvas,0,0,$white);
            imagealphablending($canvas,true);imagecopyresampled($canvas,$source,0,0,0,0,$width,$height,$sourceW,$sourceH);
            ob_start();imagejpeg($canvas,null,90);$jpeg=ob_get_clean();imagedestroy($canvas);imagedestroy($source);
            return is_string($jpeg)?['data'=>$jpeg,'width'=>$width,'height'=>$height]:null;
        }
        if(($info['mime']??'')==='image/jpeg')return ['data'=>$raw,'width'=>(int)$info[0],'height'=>(int)$info[1]];
        return null;
    }

    private static function pdf(array $streams, ?array $logo = null): string
    {
        $count = count($streams);
        $font = 3 + $count * 2;
        $bold = $font + 1;
        $logoObject = $logo ? $bold + 1 : null;
        $objects = ['<< /Type /Catalog /Pages 2 0 R >>'];
        $kids = [];
        for ($i = 0; $i < $count; $i++) {
            $kids[] = (3 + $i * 2) . ' 0 R';
        }
        $objects[] = '<< /Type /Pages /Kids [' . implode(' ', $kids) . '] /Count ' . $count . ' >>';
        foreach ($streams as $i => $stream) {
            $page = 3 + $i * 2;
            $xObject = $logoObject ? ' /XObject << /Logo ' . $logoObject . ' 0 R >>' : '';
            $objects[] = '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 '
                . $font . ' 0 R /F2 ' . $bold . ' 0 R >>' . $xObject . ' >> /Contents ' . ($page + 1) . ' 0 R >>';
            $objects[] = '<< /Length ' . strlen($stream) . ">>\nstream\n" . $stream . "\nendstream";
        }
        $objects[] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>';
        $objects[] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>';
        if($logo){$objects[]='<< /Type /XObject /Subtype /Image /Width '.$logo['width'].' /Height '.$logo['height'].' /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length '.strlen($logo['data']).">>\nstream\n".$logo['data']."\nendstream";}
        $pdf = "%PDF-1.4\n";
        $offset = [0];
        foreach ($objects as $i => $object) {
            $offset[] = strlen($pdf);
            $pdf .= ($i + 1) . " 0 obj\n$object\nendobj\n";
        }
        $xref = strlen($pdf);
        $pdf .= "xref\n0 " . (count($objects) + 1) . "\n0000000000 65535 f \n";
        for ($i = 1; $i <= count($objects); $i++) {
            $pdf .= sprintf('%010d 00000 n ', $offset[$i]) . "\n";
        }
        return $pdf . 'trailer << /Size ' . (count($objects) + 1) . ' /Root 1 0 R >>'
            . "\nstartxref\n$xref\n%%EOF";
    }
    private static function text(float$x,float$y,float$size,string$text,bool$bold=false,string$align='L',array$color=[0,0,0]):string{$text=self::latin(self::cut($text,130));$width=strlen($text)*$size*.48;if($align==='R')$x-=$width;if($align==='C')$x-=$width/2;return sprintf('BT %.3F %.3F %.3F rg /%s %.2F Tf %.2F %.2F Td (%s) Tj ET',$color[0],$color[1],$color[2],$bold?'F2':'F1',$size,$x,$y,self::escape($text));}
    private static function fill(float$x,float$y,float$w,float$h,float$r,float$g,float$b):string{return sprintf('%.3F %.3F %.3F rg %.2F %.2F %.2F %.2F re f',$r,$g,$b,$x,$y,$w,$h);}
    private static function rect(float$x,float$y,float$w,float$h,float$r,float$g,float$b):string{return sprintf('%.3F %.3F %.3F RG %.2F %.2F %.2F %.2F re S',$r,$g,$b,$x,$y,$w,$h);}
    private static function line(float$x1,float$y1,float$x2,float$y2,float$r,float$g,float$b,float$width):string{return sprintf('%.3F %.3F %.3F RG %.2F w %.2F %.2F m %.2F %.2F l S',$r,$g,$b,$width,$x1,$y1,$x2,$y2);}
    private static function strokeHex(float$x,float$y,float$s,float$r,float$g,float$b):string{$h=$s*.866;$pts=[[$x+$s*.25,$y],[$x+$s*.75,$y],[$x+$s,$y+$h/2],[$x+$s*.75,$y+$h],[$x+$s*.25,$y+$h],[$x,$y+$h/2]];$cmd=sprintf('%.3F %.3F %.3F RG .5 w ',$r,$g,$b);foreach($pts as$i=>$p)$cmd.=sprintf('%.2F %.2F %s ',$p[0],$p[1],$i?'l':'m');return$cmd.'h S';}
    private static function initials(string$v):string{$parts=preg_split('/\s+/',$v)?:[];return strtoupper(substr($parts[0]??'G',0,1).substr($parts[1]??'C',0,1));}
    private static function cut(string$v,int$n):string{$v=preg_replace('/\s+/u',' ',trim($v))??'';return mb_strlen($v)>$n?mb_substr($v,0,$n-1).'…':$v;}
    private static function money(mixed$v):string{return number_format((float)$v,2,',',' ');}
    private static function qty(mixed$v):string{$n=(float)$v;return abs($n-round($n))<.000001?(string)(int)round($n):rtrim(rtrim(number_format($n,3,',',''),'0'),',');}
    private static function date(mixed$v):string{if(!$v)return'-';$t=strtotime((string)$v);return$t?date('d/m/Y',$t):(string)$v;}
    private static function latin(string$v):string{return iconv('UTF-8','Windows-1252//TRANSLIT',$v)?:$v;}
    private static function escape(string$v):string{return str_replace(['\\','(',')'],['\\\\','\(','\)'],$v);}
}
