import {build_controls} from "../protocol/gallery_descriptor";
import {parse_gallery_response} from "../protocol/gallery_parser";
import type {Gallery_Case, Gallery_Frame_Mode, Gallery_Performance_Capture, Gallery_Render_Plan, Json_Primitive, Json_Value} from "../protocol/gallery_types";
import {Gallery_Socket, gallery_socket_url} from "../transport/gallery_socket";
import {Client_Performance} from "../runtime/client_performance";
import {Frame_Request_Controller} from "../runtime/frame_request_controller";
import {Pixel_Presenter} from "../runtime/pixel_presenter";
import {EMPTY_PLOT_SNAPSHOT, type Gallery_Plot_Snapshot} from "./gallery_plot_snapshot";

export class Gallery_Plot_Session {
    private snapshot: Gallery_Plot_Snapshot = EMPTY_PLOT_SNAPSHOT;
    private readonly listeners = new Set<() => void>();
    private readonly socket: Gallery_Socket;
    private readonly presenter = new Pixel_Presenter();
    private readonly client_performance = new Client_Performance();
    private readonly frame_controller: Frame_Request_Controller;
    private disposed = false; private visible = false; private streams_paused = false; private reconnect_timer: number | undefined; private last_observe_at = 0; private sent_width = 0; private sent_height = 0; private received_frames = 0;
    constructor(readonly gallery_case: Gallery_Case, readonly frame_mode: Gallery_Frame_Mode) {
        this.socket = new Gallery_Socket(gallery_socket_url(), {on_json: raw=>this.receive_json(raw),on_binary: buffer=>this.receive_pixels(buffer),on_state: state=>this.receive_socket_state(state)});
        this.frame_controller = new Frame_Request_Controller(()=>this.socket.send("frame"),()=>{this.client_performance.record_timeout();if(this.stream_active())this.request_frame(performance.now());},(now,value)=>this.client_performance.record_round_trip(now,value));
    }
    subscribe = (listener: () => void): (() => void) => {this.listeners.add(listener);return()=>this.listeners.delete(listener);};
    get_snapshot = (): Gallery_Plot_Snapshot => this.snapshot;
    private update(patch: Partial<Gallery_Plot_Snapshot>): void {this.snapshot={...this.snapshot,...patch};this.listeners.forEach(listener=>listener());}
    connect(): void {if(this.disposed)return;window.clearTimeout(this.reconnect_timer);this.socket.connect();}
    dispose(): void {this.disposed=true;window.clearTimeout(this.reconnect_timer);this.frame_controller.cancel();if(this.snapshot.ready)this.socket.send("hide");this.socket.close();this.presenter.detach();this.listeners.clear();}
    attach_canvas(canvas: HTMLCanvasElement): void {this.presenter.attach(canvas);}
    detach_canvas(): void {this.presenter.detach();}
    set_activity(visible: boolean, streams_paused: boolean): void {const was_active=this.stream_active();this.visible=visible;this.streams_paused=streams_paused;if(visible&&!this.socket.is_open())this.connect();const active=this.stream_active();if(this.snapshot.ready&&active!==was_active)this.socket.send(active?"show":"hide");if(active)this.request_frame(performance.now());}
    private stream_active(): boolean {return this.visible&&!this.streams_paused;}
    private receive_socket_state(state: "connecting"|"ready"|"error"|"closed"): void {if(this.disposed)return;if(state==="ready"){this.update({connection_state:"ready",protocol_error:""});this.socket.send("gallery_open",{case:this.gallery_case.id,frame_mode:this.frame_mode.id});}else{this.update({connection_state:state,ready:state==="error"?this.snapshot.ready:false});if(state==="closed"&&this.visible)this.reconnect_timer=window.setTimeout(()=>this.connect(),1000);}}
    private receive_json(raw: string): void {try{const response=parse_gallery_response(raw);if(!response)return;if(response.type==="error"){this.update({notice:Object.values(response.field_errors??{})[0]??response.message});return;}if(response.type==="observer_state"){this.update({telemetry:response.telemetry,performance_capture:response.telemetry.performance_capture??this.snapshot.performance_capture,frame_count:this.received_frames});return;}if(response.type==="case_state"||response.type==="refresh_state"){const controls=response.controls;this.update({ready:true,controls:build_controls(controls?.resources),actions:response.actions?.data??[],telemetry:response.telemetry??{},render_plan:controls?.render_plan??null,performance_capture:controls?.performance_capture??response.telemetry?.performance_capture??null,notice:response.notice??"",frame_count:this.received_frames,protocol_error:""});if(this.stream_active()){this.socket.send("show");this.request_frame(performance.now());}}}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"协议解析失败"});this.socket.close();}}
    private receive_pixels(buffer: ArrayBuffer): void {try{const now=performance.now();this.frame_controller.receive_frame(now);const accepted=this.presenter.accept(buffer);this.received_frames++;this.client_performance.record_pixel(now,accepted.signature,accepted.overwritten);if(this.frame_mode.request_after_response&&this.stream_active())this.request_frame(now);}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"像素协议错误"});this.socket.close();}}
    tick(now: number): void {if(!this.stream_active())return;if(this.presenter.present())this.client_performance.record_presentation(now);if(this.frame_mode.request_on_animation_frame)this.request_frame(now);if(this.snapshot.ready&&now-this.last_observe_at>=650){this.last_observe_at=now;this.socket.send("gallery_observe",{client_metrics:this.client_performance.snapshot(now,this.socket.buffered_bytes()) as unknown as Json_Value});}}
    request_frame(now=performance.now(), explicit=false): boolean {if(!this.snapshot.ready||!this.socket.is_open()||(!explicit&&!this.stream_active())||(explicit&&!this.visible)||(!explicit&&!this.frame_mode.automatic))return false;return this.frame_controller.request_frame(now);}
    resize(width: number,height: number): void {width=Math.max(240,Math.round(width));height=Math.max(180,Math.round(height));if(width===this.sent_width&&height===this.sent_height)return;this.sent_width=width;this.sent_height=height;if(this.socket.is_open())this.socket.send("resize",{width,height});}
    pointer(type: "pointer_move"|"pointer_press"|"pointer_release", payload: Record<string, Json_Value>): void {this.socket.send(type,payload);}
    leave(): void {this.socket.send("leave");}
    wheel(payload: Record<string, Json_Value>): void {this.socket.send("wheel",payload);}
    key(type: "key_press"|"key_release",payload: Record<string, Json_Value>): void {this.socket.send(type,payload);}
    patch(target: string,patch: Record<string, Json_Value>): void {this.socket.send("gallery_patch",{target,patch});}
    action(action: string,argument?: Json_Primitive): void {this.socket.send("gallery_action",argument===undefined?{action}:{action,argument});}
    refresh(): void {this.socket.send("gallery_refresh",{client_metrics:this.client_performance.snapshot(performance.now(),this.socket.buffered_bytes()) as unknown as Json_Value});}
    reset_monitoring(): void {this.client_performance.reset();this.socket.send("gallery_reset_monitoring");}
}
