<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Niwa_AI_Admin_Autonomy_Self_Improvement_Manager {
    /** @var Niwa_AI_Auth_Provider_Interface|null */
    private $auth_manager;
    /** @var Niwa_AI_AI_Client_Interface|null */
    private $openai_client;
    private $model_router;
    private $workspace;
    private $companion_memory_manager = null;

    public function __construct( ?Niwa_AI_Auth_Provider_Interface $auth_manager = null, ?Niwa_AI_AI_Client_Interface $openai_client = null, $model_router = null, $workspace = null ) {
        $this->auth_manager  = $auth_manager;
        $this->openai_client = $openai_client;
        $this->model_router  = $model_router;
        $this->workspace     = $workspace;
    }

    public function set_companion_memory_manager( $companion_memory_manager ) {
        $this->companion_memory_manager = $companion_memory_manager;
    }

    public function capture_turn_learning( $session_id, $session = array(), $turn = array() ) {
        $session_id = trim( (string) $session_id );
        $session    = is_array( $session ) ? $session : array();
        $turn       = is_array( $turn ) ? $turn : array();

        if ( '' === $session_id || empty( $turn ) || ! $this->workspace || ! method_exists( $this->workspace, 'read_session_transcript' ) ) {
            return array( 'stored' => false, 'reason' => 'missing_context' );
        }

        $status = isset( $turn['status'] ) ? sanitize_key( (string) $turn['status'] ) : '';
        if ( 'completed' !== $status ) {
            return array( 'stored' => false, 'reason' => 'turn_not_completed' );
        }

        $executed_tools = $this->extract_turn_tool_names( $turn );
        if ( empty( $executed_tools ) ) {
            return array( 'stored' => false, 'reason' => 'no_executed_tools' );
        }

        $records = (array) $this->workspace->read_session_transcript( $session_id, 36 );
        if ( ! $this->transcript_has_learning_signal( $records ) ) {
            return array( 'stored' => false, 'reason' => 'no_learning_signal' );
        }

        $analysis = $this->generate_turn_learning_payload( $session_id, $records, $turn, $executed_tools );
        if ( empty( $analysis['should_store'] ) ) {
            return array( 'stored' => false, 'reason' => 'model_rejected' );
        }

        $memory_note = trim( (string) ( isset( $analysis['memory_note'] ) ? $analysis['memory_note'] : '' ) );
        if ( '' === $memory_note ) {
            return array( 'stored' => false, 'reason' => 'empty_memory_note' );
        }

        $operating_lesson = trim( (string) ( isset( $analysis['operating_lesson'] ) ? $analysis['operating_lesson'] : '' ) );
        $evidence         = trim( (string) ( isset( $analysis['evidence'] ) ? $analysis['evidence'] : '' ) );
        $category         = trim( (string) ( isset( $analysis['category'] ) ? $analysis['category'] : '' ) );
        $dedupe_key       = substr( sha1( $session_id . '|' . $memory_note . '|' . $operating_lesson ), 0, 20 );

        $daily_note = 'Session ' . $session_id . '.';
        if ( '' !== $category ) {
            $daily_note .= ' Category: ' . $category . '.';
        }
        $daily_note .= ' ' . $memory_note;
        if ( '' !== $evidence ) {
            $daily_note .= ' Evidence: ' . $evidence;
        }
        if ( ! empty( $executed_tools ) ) {
            $daily_note .= ' Tools: ' . implode( ', ', array_slice( $executed_tools, 0, 8 ) ) . '.';
        }

        $daily_result = method_exists( $this->workspace, 'append_daily_memory_entry' )
            ? (array) $this->workspace->append_daily_memory_entry( $daily_note, 'Workflow Corrections', $dedupe_key )
            : array( 'success' => false );

        $bootstrap_result = array( 'success' => false );
        if ( ! empty( $analysis['promote_to_bootstrap_memory'] ) && '' !== $operating_lesson && method_exists( $this->workspace, 'append_operating_memory_rule' ) ) {
            $bootstrap_result = (array) $this->workspace->append_operating_memory_rule( $operating_lesson, $dedupe_key, 'Operating Lessons' );
        }
        return array(
            'stored'             => ! empty( $daily_result['success'] ) || ! empty( $bootstrap_result['success'] ),
            'daily_memory_path'  => isset( $daily_result['path'] ) ? (string) $daily_result['path'] : '',
            'bootstrap_promoted' => ! empty( $bootstrap_result['success'] ),
            'lesson'             => $operating_lesson,
            'note'               => $memory_note,
        );
    }

    public function flush_session_memory( $session_id, $session = array(), $reason = 'compaction' ) {
        $session_id = trim( (string) $session_id );
        $session    = is_array( $session ) ? $session : array();
        $reason     = sanitize_key( (string) $reason );

        if ( '' === $session_id || ! $this->workspace || ! method_exists( $this->workspace, 'read_session_transcript' ) || ! method_exists( $this->workspace, 'append_operating_memory_rule' ) ) {
            return array( 'stored' => false, 'reason' => 'missing_context' );
        }

        $records = (array) $this->workspace->read_session_transcript( $session_id, 28 );
        $worklog = isset( $session['worklog_summary'] ) ? trim( (string) $session['worklog_summary'] ) : '';
        if ( empty( $records ) && '' === $worklog ) {
            return array( 'stored' => false, 'reason' => 'empty_session' );
        }

        $analysis = $this->generate_session_flush_payload( $session_id, $session, $records, $reason );
        if ( empty( $analysis['should_store'] ) ) {
            return array( 'stored' => false, 'reason' => 'model_rejected' );
        }

        $summary = trim( (string) ( isset( $analysis['summary'] ) ? $analysis['summary'] : '' ) );
        if ( '' === $summary ) {
            return array( 'stored' => false, 'reason' => 'empty_summary' );
        }

        $dedupe_key = substr( sha1( $session_id . '|' . $reason . '|' . $summary ), 0, 20 );
        $note       = 'Session ' . $session_id . ' ' . $reason . ' flush: ' . $summary;
        $result     = (array) $this->workspace->append_operating_memory_rule( $note, $dedupe_key, 'Session Flushes' );

        return array(
            'stored' => ! empty( $result['success'] ),
            'path'   => isset( $result['path'] ) ? (string) $result['path'] : '',
            'note'   => $summary,
        );
    }

    private function generate_turn_learning_payload( $session_id, $records, $turn, $executed_tools ) {
        $fallback = array(
            'should_store'               => false,
            'category'                   => '',
            'operating_lesson'           => '',
            'memory_note'                => '',
            'evidence'                   => '',
            'promote_to_bootstrap_memory' => false,
        );

        if ( ! $this->auth_manager || ! $this->openai_client ) {
            return $fallback;
        }

        $prompt = implode(
            "\n",
            array(
                'Session ID: ' . $session_id,
                'Executed tools in the final successful turn: ' . implode( ', ', (array) $executed_tools ),
                '',
                '[RECENT TRANSCRIPT]',
                $this->format_transcript_for_prompt( $records ),
                '[/RECENT TRANSCRIPT]',
                '',
                '[FINAL TURN]',
                $this->format_turn_for_prompt( $turn ),
                '[/FINAL TURN]',
            )
        );

        $instructions = implode(
            "\n",
            array(
                'You are extracting self-improvement lessons for Niwa after a successful recovery from a mistake or correction.',
                'Store a lesson ONLY if the transcript shows one of these patterns: false success claim corrected by the founder, saying tools were unavailable before checking, wrong tool family, repeated prompting needed before execution, or a similar stable workflow mistake that was later corrected successfully.',
                'Do not store one-off content facts. Do not store ordinary task summaries without a real correction/recovery pattern.',
                'operating_lesson must be a short general rule Niwa should follow in future turns.',
                'memory_note must be a concise factual recap of what changed and why it matters.',
                'evidence must mention the failure pattern and the corrected successful path.',
                'promote_to_bootstrap_memory should be true only for general operating rules that should remain always-on in operational memory.',
                'Return minified JSON only with keys: should_store, category, operating_lesson, memory_note, evidence, promote_to_bootstrap_memory.',
            )
        );

        return $this->generate_json_payload( $prompt, $instructions, $fallback, 220 );
    }

    private function generate_session_flush_payload( $session_id, $session, $records, $reason ) {
        $fallback = array(
            'should_store' => false,
            'summary'      => '',
        );

        if ( ! $this->auth_manager || ! $this->openai_client ) {
            return $fallback;
        }

        $worklog = isset( $session['worklog_summary'] ) ? trim( (string) $session['worklog_summary'] ) : '';
        $notes   = array();
        foreach ( isset( $session['sticky_notes'] ) && is_array( $session['sticky_notes'] ) ? array_slice( $session['sticky_notes'], -6 ) : array() as $note ) {
            if ( is_array( $note ) && ! empty( $note['text'] ) ) {
                $notes[] = '- ' . trim( preg_replace( '/\s+/u', ' ', (string) $note['text'] ) );
            }
        }

        $prompt = implode(
            "\n",
            array(
                'Session ID: ' . $session_id,
                'Flush reason: ' . $reason,
                '',
                '[WORKLOG]',
                '' !== $worklog ? $worklog : 'None.',
                '[/WORKLOG]',
                '',
                '[STICKY NOTES]',
                ! empty( $notes ) ? implode( "\n", $notes ) : 'None.',
                '[/STICKY NOTES]',
                '',
                '[RECENT TRANSCRIPT]',
                $this->format_transcript_for_prompt( $records ),
                '[/RECENT TRANSCRIPT]',
            )
        );

        $instructions = implode(
            "\n",
            array(
                'You are writing a durable operational session flush summary for Niwa.',
                'Store only lasting operational value: corrected workflows, important successful fixes, real blocked technical threads worth remembering, and proven execution facts.',
                'Do not store companion identity, relationship tone, personal founder facts, affection, jokes, or raw conversation continuity here.',
                'Do not restate casual chat. Do not dump raw tool payloads. Do not create long summaries.',
                'Return minified JSON only with keys: should_store, summary.',
                'summary must be one compact paragraph, max 120 words.',
            )
        );

        return $this->generate_json_payload( $prompt, $instructions, $fallback, 180 );
    }

    private function generate_json_payload( $prompt, $instructions, $fallback, $max_tokens = 200 ) {
        $fallback = is_array( $fallback ) ? $fallback : array();
        if ( ! $this->auth_manager || ! $this->openai_client ) {
            return $fallback;
        }

        $model     = $this->resolve_model();
        $auth_mode = $this->auth_manager->get_mode();
        $creds     = $this->auth_manager->get_credentials();
        $result    = $this->openai_client->generate(
            $model,
            $prompt,
            $instructions,
            $auth_mode,
            $creds,
            max( 60, intval( $max_tokens ) )
        );

        if ( empty( $result['success'] ) ) {
            return $fallback;
        }

        $answer = trim( (string) ( isset( $result['answer'] ) ? $result['answer'] : '' ) );
        if ( preg_match( '/\{.*\}/s', $answer, $matches ) ) {
            $answer = (string) $matches[0];
        }

        $decoded = json_decode( $answer, true );
        if ( ! is_array( $decoded ) ) {
            return $fallback;
        }

        return wp_parse_args( $decoded, $fallback );
    }

    private function resolve_model() {
        if ( $this->model_router && method_exists( $this->model_router, 'route_for_founder_agent' ) ) {
            return (string) $this->model_router->route_for_founder_agent();
        }
        if ( $this->model_router && method_exists( $this->model_router, 'route_for_agent' ) ) {
            return (string) $this->model_router->route_for_agent();
        }

        return 'gpt-5.4-mini';
    }

    private function transcript_has_learning_signal( $records ) {
        $records = is_array( $records ) ? $records : array();
        if ( empty( $records ) ) {
            return false;
        }

        $haystack = array();
        foreach ( array_slice( $records, -24 ) as $record ) {
            if ( ! is_array( $record ) ) {
                continue;
            }

            if ( ! empty( $record['prompt'] ) ) {
                $haystack[] = mb_strtolower( (string) $record['prompt'] );
            }
            if ( ! empty( $record['reply'] ) ) {
                $haystack[] = mb_strtolower( (string) $record['reply'] );
            }
        }

        if ( empty( $haystack ) ) {
            return false;
        }

        $text = function_exists( 'mb_strtolower' )
            ? mb_strtolower( implode( "\n", $haystack ), 'UTF-8' )
            : strtolower( implode( "\n", $haystack ) );
        $patterns = array(
            'u pravu si',
            'nije objavljeno',
            'link je la',
            'neta',
            'zar nema',
            'ali ima',
            'tool search',
            'tool load',
            'ne memory search',
            'ne glumi',
            'uradi sad',
            'izvrsi',
            'nare',
            'ne mogu',
            'nemam aktivan alat',
            'nemam pristup',
            'runtime odbio',
            'if you want',
            'cannot',
            'i can\'t',
            'not available',
        );

        foreach ( $patterns as $pattern ) {
            $pos = function_exists( 'mb_strpos' )
                ? mb_strpos( $text, $pattern, 0, 'UTF-8' )
                : strpos( $text, $pattern );
            if ( false !== $pos ) {
                return true;
            }
        }

        return false;
    }

    private function extract_turn_tool_names( $turn ) {
        $turn  = is_array( $turn ) ? $turn : array();
        $names = array();

        foreach ( isset( $turn['tool_events'] ) && is_array( $turn['tool_events'] ) ? $turn['tool_events'] : array() as $event ) {
            if ( empty( $event['name'] ) ) {
                continue;
            }
            $names[] = sanitize_key( (string) $event['name'] );
        }

        foreach ( isset( $turn['meta']['executed_tools'] ) && is_array( $turn['meta']['executed_tools'] ) ? $turn['meta']['executed_tools'] : array() as $name ) {
            $clean = sanitize_key( (string) $name );
            if ( '' !== $clean ) {
                $names[] = $clean;
            }
        }

        return array_values( array_unique( array_filter( $names ) ) );
    }

    private function format_transcript_for_prompt( $records ) {
        $records = is_array( $records ) ? $records : array();
        if ( empty( $records ) ) {
            return 'No transcript.';
        }

        $lines = array();
        foreach ( array_slice( $records, -24 ) as $record ) {
            if ( ! is_array( $record ) ) {
                continue;
            }

            $type = isset( $record['type'] ) ? (string) $record['type'] : '';
            if ( 'turn_start' === $type && ! empty( $record['prompt'] ) ) {
                $lines[] = 'User: ' . $this->flatten_text( $record['prompt'], 240 );
            } elseif ( 'turn_end' === $type && ! empty( $record['reply'] ) ) {
                $lines[] = 'Assistant: ' . $this->flatten_text( $record['reply'], 240 );
            } elseif ( 'tool' === $type && ! empty( $record['tool'] ) ) {
                $lines[] = 'Tool ' . sanitize_key( (string) $record['tool'] ) . ': ' . $this->flatten_text( wp_json_encode( isset( $record['output'] ) ? $record['output'] : array() ), 220 );
            } elseif ( 'sticky_note' === $type && ! empty( $record['text'] ) ) {
                $lines[] = 'Note: ' . $this->flatten_text( $record['text'], 180 );
            } elseif ( 'session_compaction' === $type && ! empty( $record['summary'] ) ) {
                $lines[] = 'Compaction: ' . $this->flatten_text( $record['summary'], 180 );
            }
        }

        return ! empty( $lines ) ? implode( "\n", $lines ) : 'No transcript.';
    }

    private function format_turn_for_prompt( $turn ) {
        $turn  = is_array( $turn ) ? $turn : array();
        $lines = array();
        if ( ! empty( $turn['prompt'] ) ) {
            $lines[] = 'Prompt: ' . $this->flatten_text( $turn['prompt'], 240 );
        }
        if ( ! empty( $turn['reply'] ) ) {
            $lines[] = 'Reply: ' . $this->flatten_text( $turn['reply'], 260 );
        }
        if ( ! empty( $turn['tool_events'] ) && is_array( $turn['tool_events'] ) ) {
            $tool_names = $this->extract_turn_tool_names( $turn );
            if ( ! empty( $tool_names ) ) {
                $lines[] = 'Tools: ' . implode( ', ', $tool_names );
            }
        }

        return ! empty( $lines ) ? implode( "\n", $lines ) : 'No final turn data.';
    }

    private function flatten_text( $text, $limit = 220 ) {
        $text  = trim( preg_replace( '/\s+/u', ' ', (string) $text ) );
        $limit = max( 80, intval( $limit ) );
        if ( '' === $text ) {
            return '';
        }

        if ( function_exists( 'mb_strlen' ) && mb_strlen( $text, 'UTF-8' ) <= $limit ) {
            return $text;
        }
        if ( ! function_exists( 'mb_strlen' ) && strlen( $text ) <= $limit ) {
            return $text;
        }

        return function_exists( 'mb_substr' )
            ? rtrim( mb_substr( $text, 0, $limit - 3, 'UTF-8' ) ) . '...'
            : rtrim( substr( $text, 0, $limit - 3 ) ) . '...';
    }
}
