<?php

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

class Niwa_AI_Conversation_Store {
    private const DEFAULT_HISTORY_LIMIT = Niwa_AI_Constants::HISTORY_LIMIT_FRONTEND;
    private const FOUNDER_HISTORY_LIMIT_PER_ROLE = 60;
    private const FRONTEND_SESSION_PREFIX = 'frontend__';
    private const FOUNDER_SESSION_PREFIXES = array( 'admin-telegram-', 'admin-whatsapp-', 'admin-ui-', 'founder:' );
    private $workspace = null;

    public function set_workspace( $workspace ) {
        $this->workspace = $workspace;
    }

    private function key( $session_id ) {
        return 'niwa_ai_conv_' . md5( (string) $session_id );
    }

    private function persistent_key( $session_id ) {
        return 'niwa_ai_conv_p_' . md5( (string) $session_id );
    }

    private function should_persist_session( $session_id ) {
        $channel = $this->get_session_channel( $session_id );
        return in_array( $channel, array( 'founder', 'subagent' ), true );
    }

    private function get_history_limit( $session_id = '' ) {
        $channel = $this->get_session_channel( $session_id );
        $limit   = self::DEFAULT_HISTORY_LIMIT;

        if ( 'frontend' === $channel ) {
            $limit = Niwa_AI_Constants::HISTORY_LIMIT_FRONTEND;
        } elseif ( in_array( $channel, array( 'founder', 'subagent' ), true ) ) {
            $limit = Niwa_AI_Constants::HISTORY_LIMIT_FOUNDER;
        }

        if ( function_exists( 'apply_filters' ) ) {
            $limit = (int) apply_filters( 'niwa_ai_conversation_history_limit', $limit, $session_id, $channel );
        }

        return max( 1, $limit );
    }

    private function get_history_ttl( $session_id = '' ) {
        $channel = $this->get_session_channel( $session_id );
        $ttl     = 24 * HOUR_IN_SECONDS;

        if ( 'frontend' === $channel ) {
            $ttl = 12 * HOUR_IN_SECONDS;
        } elseif ( in_array( $channel, array( 'founder', 'subagent' ), true ) ) {
            $ttl = 90 * DAY_IN_SECONDS;
        }

        if ( function_exists( 'apply_filters' ) ) {
            $ttl = (int) apply_filters( 'niwa_ai_conversation_ttl', $ttl, $session_id, $channel );
        }

        return max( HOUR_IN_SECONDS, $ttl );
    }

	    public function get( $session_id ) {
	        if ( $this->should_use_workspace_session_backend( $session_id ) ) {
	            return $this->get_workspace_session_history( $session_id );
	        }

	        $items = Niwa_AI_Durable_Cache::get( $this->key( $session_id ) );
	        if ( is_array( $items ) ) {
	            return $this->apply_founder_history_window( $session_id, $items );
	        }

	        $migrated = $this->maybe_migrate_legacy_frontend_session( $session_id );
	        if ( is_array( $migrated ) ) {
	            return $this->apply_founder_history_window( $session_id, $migrated );
	        }

	        if ( ! $this->should_persist_session( $session_id ) ) {
	            return array();
	        }

	        $stored = get_option( $this->persistent_key( $session_id ), array() );
	        if ( is_array( $stored ) ) {
	            Niwa_AI_Durable_Cache::set( $this->key( $session_id ), $stored, $this->get_history_ttl( $session_id ) );
	            return $this->apply_founder_history_window( $session_id, $stored );
	        }

	        return array();
	    }

	    public function get_display_history( $session_id, $filters = array() ) {
	        $session_id = (string) $session_id;
	        $filters    = is_array( $filters ) ? $filters : array();
	        $limit      = isset( $filters['limit'] ) ? max( 1, intval( $filters['limit'] ) ) : $this->get_history_limit( $session_id );
	        $channel    = isset( $filters['channel'] ) ? sanitize_key( (string) $filters['channel'] ) : '';
	        $before_ts  = isset( $filters['before_ts'] ) ? max( 0, intval( $filters['before_ts'] ) ) : 0;
	        $since_ts   = isset( $filters['since_ts'] ) ? max( 0, intval( $filters['since_ts'] ) ) : 0;
		$before_id  = isset( $filters['before_id'] ) ? trim( (string) $filters['before_id'] ) : '';
		$since_id   = isset( $filters['since_id'] ) ? trim( (string) $filters['since_id'] ) : '';

	        if ( $this->should_use_workspace_session_backend( $session_id ) ) {
	            $has_cursor_window = isset( $filters['limit'] ) || $before_ts > 0 || $since_ts > 0;
	            if ( '' !== $channel || $has_cursor_window ) {
	                $workspace_window = min( 600, max( $limit * 8, 80 ) );
	                $records = $this->read_workspace_dialogue_records( $session_id, $workspace_window );
	                $items   = $this->build_history_from_workspace_transcript( $records, $workspace_window, $filters );
	                $items   = $this->dedupe_display_history_items( $items );
	                $items   = $this->filter_items_by_cursor_window( $items, $before_id, $since_id, $before_ts, $since_ts );
	                return $this->apply_founder_history_window( $session_id, array_slice( $items, -$limit ) );
	            }

	            return $this->dedupe_display_history_items( $this->get_workspace_session_history( $session_id ) );
	        }

	        $items = $this->get( $session_id );
	        if ( '' !== $channel ) {
	            $items = array_values(
	                array_filter(
	                    $items,
	                    function ( $item ) use ( $channel ) {
	                        if ( ! is_array( $item ) ) {
	                            return false;
	                        }

	                        $item_channel = isset( $item['channel'] ) ? sanitize_key( (string) $item['channel'] ) : '';
	                        return '' === $item_channel || $channel === $item_channel;
	                    }
	                )
	            );
	        }

	        $items = $this->dedupe_display_history_items( $items );
	        $items = $this->filter_items_by_cursor_window( $items, $before_id, $since_id, $before_ts, $since_ts );
	        return array_slice( $items, -$limit );
	    }

	    private function dedupe_display_history_items( $items ) {
	        $items      = is_array( $items ) ? array_values( $items ) : array();
	        $normalized = array();
	        $bot_turns  = array();
	        foreach ( $items as $index => $item ) {
	            if ( ! is_array( $item ) ) {
	                continue;
	            }

	            $id = isset( $item['id'] ) ? trim( (string) $item['id'] ) : '';
			if ( '' === $id ) {
				$id         = 'legacy_' . md5( wp_json_encode( $item ) . '|' . intval( $index ) );
				$item['id'] = $id;
			}
			$normalized[] = $item;
			$role    = strtolower( (string) ( $item['role'] ?? '' ) );
			$turn_id = trim( (string) ( $item['turn_id'] ?? '' ) );
			if ( in_array( $role, array( 'assistant', 'bot' ), true ) && '' !== $turn_id ) {
				$bot_turns[ $turn_id ] = true;
			}
		}

		$preferred = array();
		foreach ( $normalized as $index => $item ) {
			$id = (string) $item['id'];
			if ( ! isset( $preferred[ $id ] ) ) {
				$preferred[ $id ] = $index;
				continue;
			}

			$existing      = $normalized[ $preferred[ $id ] ];
			$existing_turn = trim( (string) ( $existing['turn_id'] ?? '' ) );
			$current_turn  = trim( (string) ( $item['turn_id'] ?? '' ) );
			if ( ! isset( $bot_turns[ $existing_turn ] ) && isset( $bot_turns[ $current_turn ] ) ) {
				$preferred[ $id ] = $index;
			}
		}

		$out = array();
		foreach ( $normalized as $index => $item ) {
			if ( $preferred[ (string) $item['id'] ] !== $index ) {
				continue;
			}
			$out[] = $item;
	        }

	        return $out;
	    }

	private function filter_items_by_cursor_window( $items, $before_id = '', $since_id = '', $before_ts = 0, $since_ts = 0 ) {
		$items     = is_array( $items ) ? array_values( $items ) : array();
		$before_id = trim( (string) $before_id );
		$since_id  = trim( (string) $since_id );
		if ( '' !== $before_id || '' !== $since_id ) {
			foreach ( $items as $index => $item ) {
				$id = is_array( $item ) && isset( $item['id'] ) ? (string) $item['id'] : '';
				if ( '' !== $before_id && $id === $before_id ) {
					return array_slice( $items, 0, $index );
				}
				if ( '' !== $since_id && $id === $since_id ) {
					return array_slice( $items, $index + 1 );
				}
			}
		}
		if ( '' !== $since_id && $since_ts > 0 ) {
			$since_ts--;
		}

		return $this->filter_items_by_timestamp_window( $items, $before_ts, $since_ts );
	}

	    private function filter_items_by_timestamp_window( $items, $before_ts = 0, $since_ts = 0 ) {
	        $items     = is_array( $items ) ? array_values( $items ) : array();
	        $before_ts = max( 0, intval( $before_ts ) );
	        $since_ts  = max( 0, intval( $since_ts ) );

	        if ( $before_ts <= 0 && $since_ts <= 0 ) {
	            return $items;
	        }

	        return array_values(
	            array_filter(
	                $items,
	                function ( $item ) use ( $before_ts, $since_ts ) {
	                    if ( ! is_array( $item ) ) {
	                        return false;
	                    }

	                    $ts = isset( $item['ts'] ) ? intval( $item['ts'] ) : ( isset( $item['timestamp'] ) ? intval( $item['timestamp'] ) : 0 );
	                    if ( $ts <= 0 ) {
	                        return false;
	                    }

	                    if ( $before_ts > 0 && $ts >= $before_ts ) {
	                        return false;
	                    }

	                    if ( $since_ts > 0 && $ts <= $since_ts ) {
	                        return false;
	                    }

	                    return true;
	                }
	            )
	        );
	    }

    public function append( $session_id, $role, $text, $cards = array(), $meta = array() ) {
        $raw_role = sanitize_key( (string) $role );
        $raw_text = (string) $text;
        $meta     = is_array( $meta ) ? $meta : array();
        $new_item = array(
			'id'    => 'conv_' . str_replace( '-', '', wp_generate_uuid4() ),
            'role'  => $raw_role,
            'text'  => sanitize_textarea_field( $raw_text ),
            'cards' => is_array( $cards ) ? $cards : array(),
            'ts'    => time(),
        );

        if ( $this->should_use_workspace_session_backend( $session_id ) ) {
            Niwa_AI_Durable_Cache::delete( $this->key( $session_id ) );
            if ( ! $this->workspace_has_correlated_message( $session_id, $raw_role, $raw_text, $meta ) ) {
                $turn_id = isset( $meta['turn_id'] ) ? trim( (string) $meta['turn_id'] ) : '';
                if ( '' === $turn_id ) {
                    $turn_id = $new_item['id'];
                }
                $entry = array(
                    'type'    => 'user' === $raw_role ? 'turn_start' : 'turn_end',
                    'turn_id' => $turn_id,
                    'meta'    => $meta,
                    'ts'      => $new_item['ts'],
                );
                if ( 'user' === $raw_role ) {
                    $entry['mode']   = 'conversation';
                    $entry['prompt'] = $raw_text;
                } else {
                    $entry['status'] = 'completed';
                    $entry['reply']  = $raw_text;
                }
                if ( ! empty( $new_item['cards'] ) ) {
                    $entry['meta']['cards'] = $new_item['cards'];
                }
				if ( ! $this->workspace->append_session_transcript( $session_id, $entry ) && ! $this->workspace_has_correlated_message( $session_id, $raw_role, $raw_text, $meta ) ) {
					return false;
				}
			}
            return $this->get_workspace_session_history( $session_id );
        }

        $items = $this->get( $session_id );
		$items[] = array(
			'id' => $new_item['id'],
            'role' => $new_item['role'],
            'text' => $new_item['text'],
            'cards' => $new_item['cards'],
            'ts' => $new_item['ts'],
			'request_id' => (string) ( $meta['request_id'] ?? '' ),
			'job_id' => (string) ( $meta['admin_ui_job_id'] ?? '' ),
			'admin_ui_sequence' => intval( $meta['admin_ui_sequence'] ?? 0 ),
			'delivery_committed' => ! empty( $meta['delivery_committed'] ),
        );
        $history_limit = $this->get_history_limit( $session_id );
        $items         = array_slice( $items, -$history_limit );
        Niwa_AI_Durable_Cache::set( $this->key( $session_id ), $items, $this->get_history_ttl( $session_id ) );
        if ( $this->should_persist_session( $session_id ) ) {
            update_option( $this->persistent_key( $session_id ), $items, false );
        }
        return $this->apply_founder_history_window( $session_id, $items );
    }

    public function clear( $session_id ) {
        Niwa_AI_Durable_Cache::delete( $this->key( $session_id ) );
        if ( $this->should_persist_session( $session_id ) ) {
            delete_option( $this->persistent_key( $session_id ) );
        }
    }

    public function replace( $session_id, $items ) {
        if ( $this->should_use_workspace_session_backend( $session_id ) ) {
            Niwa_AI_Durable_Cache::delete( $this->key( $session_id ) );
            return $this->get_workspace_session_history( $session_id );
        }

        $items = is_array( $items ) ? array_values( $items ) : array();
        $items = array_slice( $items, -$this->get_history_limit( $session_id ) );
        Niwa_AI_Durable_Cache::set( $this->key( $session_id ), $items, $this->get_history_ttl( $session_id ) );
        if ( $this->should_persist_session( $session_id ) ) {
            update_option( $this->persistent_key( $session_id ), $items, false );
        }

        return $this->apply_founder_history_window( $session_id, $items );
    }

    /**
     * Get substantive conversation entries suitable for memory search.
     *
     * When a specific session_id is given, fetches only that session's
     * history. Otherwise fetches all persistent founder sessions.
     *
     * Returns flattened message entries, filtering out very short messages
     * (greetings, confirmations) to focus on substantive exchanges.
     *
     * @param string $session_id Session ID to fetch (or '' for all founder sessions).
     * @param int    $limit      Max entries to return.
     * @return array Array of [ 'role' => string, 'text' => string, 'timestamp' => int ].
     */
    public function get_searchable_entries( $session_id = '', $limit = 100 ) {
        if ( '' !== $session_id && $this->should_use_workspace_session_backend( $session_id ) ) {
            return $this->get_workspace_searchable_entries_for_session( $session_id, $limit );
        }

        if ( '' === $session_id && $this->has_workspace_session_backend() ) {
            return $this->get_workspace_searchable_entries( $limit );
        }

        // If a specific session_id is given, fetch only that session's history.
        if ( '' !== $session_id ) {
            $items = $this->get( $session_id );
            return $this->filter_searchable( $items, $limit );
        }

        // Otherwise fetch all persistent founder sessions.
        global $wpdb;
        $like_pattern = 'niwa_ai_conv_p_%';
        $option_rows  = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT 50",
                $like_pattern
            ),
            ARRAY_A
        );

        if ( empty( $option_rows ) ) {
            return array();
        }

        $entries = array();

        foreach ( $option_rows as $opt ) {
            $items   = maybe_unserialize( $opt['option_value'] );
            $batch   = $this->filter_searchable( $items, $limit - count( $entries ) );
            $entries = array_merge( $entries, $batch );

            if ( count( $entries ) >= $limit ) {
                break;
            }
        }

        return array_slice( $entries, 0, $limit );
    }

    /**
     * Filter conversation items to substantive searchable entries.
     *
     * @param mixed $items Raw conversation items array.
     * @param int   $limit Max entries to return.
     * @return array Filtered entries.
     */
    private function filter_searchable( $items, $limit ) {
        if ( ! is_array( $items ) ) {
            return array();
        }

        $entries = array();

        foreach ( $items as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }

            $role = isset( $item['role'] ) ? (string) $item['role'] : '';
            $text = isset( $item['text'] ) ? (string) $item['text'] : '';

            if ( '' === trim( $text ) ) {
                continue;
            }
            if ( ! in_array( $role, array( 'user', 'assistant', 'bot' ), true ) ) {
                continue;
            }

            $entries[] = array(
                'role'      => $role,
                'text'      => $text,
                'timestamp' => isset( $item['timestamp'] ) ? intval( $item['timestamp'] ) : ( isset( $item['ts'] ) ? intval( $item['ts'] ) : 0 ),
            );

            if ( count( $entries ) >= $limit ) {
                break;
            }
        }

        return $entries;
    }

    private function get_session_channel( $session_id ) {
        $session_id = (string) $session_id;
        if ( 0 === strpos( $session_id, self::FRONTEND_SESSION_PREFIX ) ) {
            return 'frontend';
        }
        foreach ( self::FOUNDER_SESSION_PREFIXES as $prefix ) {
            if ( 0 === strpos( $session_id, $prefix ) ) {
                return 'founder';
            }
        }
        if ( false !== strpos( $session_id, ':subagent:' ) ) {
            return 'subagent';
        }

        return 'generic';
    }

    private function has_workspace_session_backend() {
        return $this->workspace
            && method_exists( $this->workspace, 'read_session_manifest' )
            && method_exists( $this->workspace, 'read_session_transcript' )
            && method_exists( $this->workspace, 'append_session_transcript' )
            && method_exists( $this->workspace, 'list_session_manifests' );
    }

    private function workspace_has_correlated_message( $session_id, $role, $text, $meta ) {
        $meta         = is_array( $meta ) ? $meta : array();
        $correlations = array();
        foreach ( array( 'turn_id', 'request_id', 'admin_ui_job_id', 'actor_message_id', 'message_id', 'delivery_committed' ) as $key ) {
            if ( ! empty( $meta[ $key ] ) ) {
                $correlations[ $key ] = (string) $meta[ $key ];
            }
        }
        if ( empty( $correlations ) ) {
            return false;
        }
		if ( method_exists( $this->workspace, 'has_session_transcript_message' ) ) {
			return (bool) $this->workspace->has_session_transcript_message( $session_id, $role, $text, $correlations );
		}

        foreach ( array_reverse( (array) $this->workspace->read_session_transcript( $session_id, 24 ) ) as $record ) {
            if ( ! is_array( $record ) ) {
                continue;
            }
            $record_meta = isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
            $matched     = ! empty( $correlations['turn_id'] ) && $correlations['turn_id'] === (string) ( $record['turn_id'] ?? '' );
            foreach ( array( 'request_id', 'admin_ui_job_id', 'actor_message_id', 'message_id' ) as $key ) {
                if ( ! $matched && ! empty( $correlations[ $key ] ) && $correlations[ $key ] === (string) ( $record_meta[ $key ] ?? '' ) ) {
                    $matched = true;
                }
            }
            if ( ! $matched ) {
                continue;
            }
			if ( ! empty( $correlations['delivery_committed'] ) && empty( $record_meta['delivery_committed'] ) ) {
				continue;
			}
            if ( 'user' === $role && 'turn_start' === (string) ( $record['type'] ?? '' ) ) {
                if ( (string) ( $record['prompt'] ?? '' ) === (string) $text ) {
                    return true;
                }
                continue;
            }
            if ( in_array( $role, array( 'assistant', 'bot' ), true ) && 'turn_end' === (string) ( $record['type'] ?? '' ) ) {
                if ( (string) ( $record['reply'] ?? '' ) === (string) $text ) {
                    return true;
                }
            }
        }

        return false;
    }

    private function should_use_workspace_session_backend( $session_id ) {
        return $this->should_persist_session( $session_id ) && $this->has_workspace_session_backend();
    }

    private function get_workspace_session_history( $session_id ) {
        $history_limit = $this->get_history_limit( $session_id );
        $records       = $this->read_workspace_dialogue_records( $session_id, min( 600, max( $history_limit * 6, 80 ) ) );
        if ( ! empty( $records ) ) {
            $items = $this->build_history_from_workspace_transcript( $records, $history_limit );
			$items = $this->dedupe_display_history_items( $items );
            return $this->apply_founder_history_window( $session_id, array_slice( $items, -$history_limit ) );
        }

        $manifest = (array) $this->workspace->read_session_manifest( $session_id );
        if ( empty( $manifest['recent_turns'] ) || ! is_array( $manifest['recent_turns'] ) ) {
            return array();
        }

        $items = $this->build_history_from_workspace_turns( (array) $manifest['recent_turns'], $history_limit );
		$items = $this->dedupe_display_history_items( $items );
		return $this->apply_founder_history_window( $session_id, array_slice( $items, -$history_limit ) );
    }

	private function read_workspace_dialogue_records( $session_id, $limit ) {
		if ( method_exists( $this->workspace, 'read_session_dialogue_transcript' ) ) {
			return (array) $this->workspace->read_session_dialogue_transcript( $session_id, $limit );
		}

		return (array) $this->workspace->read_session_transcript( $session_id, $limit );
	}

	    private function build_history_from_workspace_turns( $turns, $history_limit, $filters = array() ) {
	        $items = array();
	        foreach ( (array) $turns as $index => $turn ) {
	            if ( ! is_array( $turn ) ) {
	                continue;
	            }

	            if ( $this->is_internal_history_record( $turn ) ) {
	                continue;
	            }

	            if ( ! $this->record_matches_history_filters( $turn, $filters ) ) {
	                continue;
	            }

	            $prompt = isset( $turn['prompt'] ) ? trim( (string) $turn['prompt'] ) : '';
	            if ( '' !== $prompt && '[continuation]' !== $prompt ) {
				$item = array(
					'id'    => $this->workspace_history_item_id( $turn, 'user', $index ),
                    'role'  => 'user',
                    'text'  => $prompt,
                    'cards' => array(),
                    'ts'    => isset( $turn['started_at'] ) ? intval( $turn['started_at'] ) : 0,
				);
				$items[] = $this->apply_history_correlation_meta( $item, $turn );
            }

	            $reply = $this->is_uncommitted_admin_ui_reply( $turn ) ? '' : ( isset( $turn['reply'] ) ? trim( (string) $turn['reply'] ) : '' );
            if ( '' !== $reply ) {
                $item = array(
					'id'    => $this->workspace_history_item_id( $turn, 'bot', $index ),
                    'role'  => 'bot',
                    'text'  => $reply,
                    'cards' => array(),
                    'ts'    => isset( $turn['ended_at'] ) ? intval( $turn['ended_at'] ) : 0,
                );
				$item = $this->apply_history_correlation_meta( $item, $turn );
                $automated_source = $this->get_automated_delivery_source( $turn );
                if ( '' !== $automated_source ) {
                    $item['source'] = $automated_source;
                }
                $items[] = $item;
            }
        }

	        return array_slice( $items, -max( 1, intval( $history_limit ) ) );
	    }

	    private function build_history_from_workspace_transcript( $records, $history_limit, $filters = array() ) {
	        $items     = array();
	        $turn_meta = array();
	        foreach ( (array) $records as $record ) {
	            if ( ! is_array( $record ) || 'turn_start' !== (string) ( $record['type'] ?? '' ) ) {
	                continue;
	            }
	            $turn_id = trim( (string) ( $record['turn_id'] ?? '' ) );
	            if ( '' !== $turn_id ) {
	                $turn_meta[ $turn_id ] = isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
	            }
	        }

	        foreach ( (array) $records as $index => $record ) {
	            if ( ! is_array( $record ) ) {
	                continue;
	            }
	            $type    = isset( $record['type'] ) ? (string) $record['type'] : '';
	            $turn_id = trim( (string) ( $record['turn_id'] ?? '' ) );
	            if ( 'turn_end' === $type && '' !== $turn_id && isset( $turn_meta[ $turn_id ] ) ) {
	                $record['meta'] = array_merge(
	                    $turn_meta[ $turn_id ],
	                    isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array()
	                );
	            }

	            if ( $this->is_internal_history_record( $record ) ) {
	                continue;
	            }

	            if ( ! $this->record_matches_history_filters( $record, $filters ) ) {
	                continue;
	            }

	            if ( 'turn_start' === $type ) {
                $prompt = isset( $record['prompt'] ) ? trim( (string) $record['prompt'] ) : '';
                if ( '' !== $prompt && '[continuation]' !== $prompt ) {
					$item = array(
						'id'    => $this->workspace_history_item_id( $record, 'user', $index ),
                        'role'  => 'user',
                        'text'  => $prompt,
                        'cards' => array(),
                        'ts'    => isset( $record['ts'] ) ? intval( $record['ts'] ) : 0,
					);
					$items[] = $this->apply_history_correlation_meta( $item, $record );
                }
	            } elseif ( 'turn_end' === $type && ! $this->is_uncommitted_admin_ui_reply( $record ) ) {
                $reply = isset( $record['reply'] ) ? trim( (string) $record['reply'] ) : '';
                if ( '' !== $reply ) {
                    $item = array(
					'id'    => $this->workspace_history_item_id( $record, 'bot', $index ),
                        'role'  => 'bot',
                        'text'  => $reply,
                        'cards' => array(),
                        'ts'    => isset( $record['ts'] ) ? intval( $record['ts'] ) : 0,
                    );
					$item = $this->apply_history_correlation_meta( $item, $record );
                    $automated_source = $this->get_automated_delivery_source( $record );
                    if ( '' !== $automated_source ) {
                        $item['source'] = $automated_source;
                    }
                    $items[] = $item;
                }
            }
        }

	        return array_slice( $items, -max( 1, intval( $history_limit ) ) );
	    }

	private function workspace_history_item_id( $record, $role, $index ) {
		$record = is_array( $record ) ? $record : array();
		$meta   = isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
		$request_id = isset( $meta['request_id'] ) ? trim( (string) $meta['request_id'] ) : '';
		if ( 'user' === (string) $role && '' !== $request_id ) {
			return $request_id;
		}
		$base   = isset( $record['turn_id'] ) ? trim( (string) $record['turn_id'] ) : '';
		if ( '' === $base && isset( $record['id'] ) ) {
			$base = trim( (string) $record['id'] );
		}
		if ( '' === $base ) {
			$base = md5( wp_json_encode( $record ) . '|' . intval( $index ) );
		}

		return 'history_' . md5( $base ) . '_' . sanitize_key( (string) $role );
	}

	private function apply_history_correlation_meta( $item, $record ) {
		$item = is_array( $item ) ? $item : array();
		$meta = is_array( $record ) && ! empty( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
		if ( ! empty( $record['turn_id'] ) ) {
			$item['turn_id'] = (string) $record['turn_id'];
		}
		if ( ! empty( $meta['request_id'] ) ) {
			$item['request_id'] = (string) $meta['request_id'];
		}
		if ( ! empty( $meta['admin_ui_job_id'] ) ) {
			$item['job_id'] = (string) $meta['admin_ui_job_id'];
		}
		if ( ! empty( $meta['admin_ui_sequence'] ) ) {
			$item['admin_ui_sequence'] = intval( $meta['admin_ui_sequence'] );
		}
		if ( ! empty( $meta['delivery_committed'] ) ) {
			$item['delivery_committed'] = true;
		}

		return $item;
	}

	private function is_uncommitted_admin_ui_reply( $record ) {
		$meta = is_array( $record ) && isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
		return ! empty( $meta['admin_ui_pre_delivery'] ) && empty( $meta['delivery_committed'] );
	}

	    private function record_matches_history_filters( $record, $filters = array() ) {
	        $filters = is_array( $filters ) ? $filters : array();
	        $channel = isset( $filters['channel'] ) ? sanitize_key( (string) $filters['channel'] ) : '';
	        if ( '' === $channel ) {
	            return true;
	        }

	        $meta         = isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
	        $record_channel = isset( $meta['channel'] ) ? sanitize_key( (string) $meta['channel'] ) : '';

	        return '' !== $record_channel && $channel === $record_channel;
	    }

	    /**
	     * Detect messages that were delivered into the founder chat by an
	     * automated runtime (scheduled job, heartbeat, proactive task) instead
	     * of being a direct reply in the live conversation. Returns the source
	     * key or '' for normal conversational messages.
	     */
	    private function get_automated_delivery_source( $record ) {
	        if ( ! is_array( $record ) ) {
	            return '';
	        }

	        $meta = isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
	        if ( empty( $meta ) ) {
	            return '';
	        }

	        $source = isset( $meta['source'] ) ? sanitize_key( (string) $meta['source'] ) : '';
	        $automated_sources = array( 'scheduled_job', 'operative_heartbeat', 'scheduled_self_wake', 'heartbeat', 'proactive_task', 'autonomous_task' );
	        if ( in_array( $source, $automated_sources, true ) ) {
	            return $source;
	        }

	        foreach ( array( 'is_heartbeat', 'is_operative_heartbeat' ) as $flag ) {
	            if ( ! array_key_exists( $flag, $meta ) ) {
	                continue;
	            }
	            $value = $meta[ $flag ];
	            if ( true === $value || 'true' === strtolower( (string) $value ) || '1' === (string) $value ) {
	                return 'heartbeat';
	            }
	        }

	        return '';
	    }

	    private function is_internal_history_record( $record ) {
	        if ( ! is_array( $record ) ) {
	            return false;
	        }

	        $meta = isset( $record['meta'] ) && is_array( $record['meta'] ) ? $record['meta'] : array();
	        if ( empty( $meta ) ) {
	            return false;
	        }

	        $visibility = isset( $meta['visibility'] ) ? sanitize_key( (string) $meta['visibility'] ) : '';
	        if ( 'internal' === $visibility || 'hidden' === $visibility ) {
	            return true;
	        }

	        if ( array_key_exists( 'internal', $meta ) ) {
	            $internal = $meta['internal'];
	            return true === $internal || 'true' === strtolower( (string) $internal ) || '1' === (string) $internal;
	        }

	        return false;
	    }

    private function get_workspace_searchable_entries_for_session( $session_id, $limit ) {
        $limit   = max( 1, intval( $limit ) );
        $entries = $this->filter_searchable( $this->get_workspace_session_history( $session_id ), $limit );
        $manifest = (array) $this->workspace->read_session_manifest( $session_id );

        if ( ! empty( $manifest['worklog_summary'] ) ) {
            $entries[] = array(
                'role'      => 'assistant',
                'text'      => (string) $manifest['worklog_summary'],
                'timestamp' => isset( $manifest['updated_at'] ) ? intval( $manifest['updated_at'] ) : 0,
            );
        }

        if ( ! empty( $manifest['sticky_notes'] ) && is_array( $manifest['sticky_notes'] ) ) {
            foreach ( $manifest['sticky_notes'] as $note ) {
                if ( ! is_array( $note ) || empty( $note['text'] ) ) {
                    continue;
                }
                $entries[] = array(
                    'role'      => 'assistant',
                    'text'      => (string) $note['text'],
                    'timestamp' => isset( $note['recorded_at'] ) ? intval( $note['recorded_at'] ) : 0,
                );
            }
        }

        return $this->dedupe_searchable_entries( $entries, $limit );
    }

    private function get_workspace_searchable_entries( $limit ) {
        $limit     = max( 1, intval( $limit ) );
        $manifests = (array) $this->workspace->list_session_manifests( max( 20, min( 50, $limit ) ) );
        $entries   = array();

        foreach ( $manifests as $manifest ) {
            if ( ! is_array( $manifest ) || empty( $manifest['session_id'] ) ) {
                continue;
            }

            $session_entries = $this->get_workspace_searchable_entries_for_session(
                (string) $manifest['session_id'],
                max( 8, min( 40, $limit - count( $entries ) ) )
            );
            $entries = array_merge( $entries, $session_entries );

            if ( count( $entries ) >= $limit ) {
                break;
            }
        }

        return array_slice( $this->dedupe_searchable_entries( $entries, $limit ), 0, $limit );
    }

    private function dedupe_searchable_entries( $entries, $limit ) {
        $deduped = array();
        $seen    = array();
        foreach ( (array) $entries as $entry ) {
            if ( ! is_array( $entry ) || empty( $entry['text'] ) ) {
                continue;
            }
            $key = md5( mb_strtolower( trim( (string) $entry['text'] ) ) );
            if ( isset( $seen[ $key ] ) ) {
                continue;
            }
            $seen[ $key ] = true;
            $deduped[]    = $entry;
            if ( count( $deduped ) >= $limit ) {
                break;
            }
        }

        return $deduped;
    }

    private function apply_founder_history_window( $session_id, $items ) {
        $items   = is_array( $items ) ? array_values( $items ) : array();
        $channel = $this->get_session_channel( $session_id );
        if ( empty( $items ) || ! in_array( $channel, array( 'founder', 'subagent' ), true ) ) {
            return $items;
        }

        $user_limit      = self::FOUNDER_HISTORY_LIMIT_PER_ROLE;
        $assistant_limit = self::FOUNDER_HISTORY_LIMIT_PER_ROLE;
        $user_count      = 0;
        $assistant_count = 0;
        $selected        = array();

        for ( $index = count( $items ) - 1; $index >= 0; $index-- ) {
            $item = isset( $items[ $index ] ) && is_array( $items[ $index ] ) ? $items[ $index ] : array();
            if ( empty( $item ) ) {
                continue;
            }

            $role = isset( $item['role'] ) ? strtolower( (string) $item['role'] ) : '';
            if ( 'user' === $role ) {
                if ( $user_count >= $user_limit ) {
                    continue;
                }
                $user_count++;
            } elseif ( in_array( $role, array( 'assistant', 'bot' ), true ) ) {
                if ( $assistant_count >= $assistant_limit ) {
                    continue;
                }
                $assistant_count++;
            }

            $selected[] = $item;
            if ( $user_count >= $user_limit && $assistant_count >= $assistant_limit ) {
                break;
            }
        }

        return array_reverse( $selected );
    }

    private function maybe_migrate_legacy_frontend_session( $session_id ) {
        $session_id = (string) $session_id;
        if ( 0 !== strpos( $session_id, self::FRONTEND_SESSION_PREFIX ) ) {
            return null;
        }

        $legacy_session = substr( $session_id, strlen( self::FRONTEND_SESSION_PREFIX ) );
        if ( '' === $legacy_session ) {
            return null;
        }

        // Try durable cache first, fall back to raw transient for pre-migration data.
        $legacy_items = Niwa_AI_Durable_Cache::get( $this->key( $legacy_session ) );
        if ( ! is_array( $legacy_items ) ) {
            $legacy_items = get_transient( $this->key( $legacy_session ) );
        }
        if ( ! is_array( $legacy_items ) ) {
            return null;
        }

        $history_limit = $this->get_history_limit( $session_id );
        $legacy_items  = array_slice( $legacy_items, -$history_limit );
        Niwa_AI_Durable_Cache::set( $this->key( $session_id ), $legacy_items, $this->get_history_ttl( $session_id ) );
        Niwa_AI_Durable_Cache::delete( $this->key( $legacy_session ) );
        delete_transient( $this->key( $legacy_session ) );

        return $legacy_items;
    }
}
