<?php

namespace HelpGent\App\Repositories;

defined( 'ABSPATH' ) || exit;

use Exception;
use WP_Session_Tokens;
use HelpGent\App\DTO\FormDTO;
use HelpGent\App\Models\Form;
use HelpGent\App\Models\FormMeta;
use HelpGent\App\Utils\Repository;
use HelpGent\WpMVC\Database\Query\Builder;
use HelpGent\WpMVC\Database\Query\JoinClause;
use stdClass;

class FormRepository {
    const DEMOMEDIAOPTIONKEY = 'helpgent_demo_medias';
    const SHARESLUGMETAKEY   = 'share_slug';

    protected function normalize_share_slug( string $value, int $fallback_id = 0 ): string {
        $slug = str_replace( '-', '_', sanitize_title( str_replace( '_', '-', $value ) ) );

        if ( '' !== $slug ) {
            return $slug;
        }

        return $fallback_id > 0 ? "form_{$fallback_id}" : 'form';
    }

    protected function get_share_slug_sources( ?string $status = null ): array {
        $query = Form::query()->select( [ 'id', 'title' ] );

        if ( ! empty( $status ) ) {
            $query->where( 'status', $status );
        }

        $forms = $query->get();

        if ( empty( $forms ) ) {
            return $forms;
        }

        $form_ids = array_values(
            array_filter(
                array_map(
                    function( $form ) {
                        return isset( $form->id ) ? intval( $form->id ) : 0;
                    },
                    $forms
                )
            )
        );

        if ( empty( $form_ids ) ) {
            return $forms;
        }

        $slug_metas = FormMeta::query()
            ->select( [ 'form_id', 'meta_value' ] )
            ->where( 'meta_key', self::SHARESLUGMETAKEY )
            ->where_in( 'form_id', $form_ids )
            ->get();

        $slug_map = [];

        foreach ( $slug_metas as $slug_meta ) {
            $form_id = intval( $slug_meta->form_id ?? 0 );

            if ( $form_id <= 0 ) {
                continue;
            }

            $slug_map[ $form_id ] = $this->normalize_share_slug(
                (string) ( $slug_meta->meta_value ?? '' ),
                $form_id
            );
        }

        return array_map(
            function( $form ) use ( $slug_map ) {
                $form_id          = isset( $form->id ) ? intval( $form->id ) : 0;
                $form->share_slug = $slug_map[ $form_id ] ?? '';

                return $form;
            },
            $forms
        );
    }

    protected function get_source_share_slug( stdClass $form ): string {
        $form_id = isset( $form->id ) ? intval( $form->id ) : 0;
        $slug    = isset( $form->share_slug ) ? (string) $form->share_slug : '';

        if ( '' === $slug ) {
            return '';
        }

        return $this->normalize_share_slug( $slug, $form_id );
    }

    protected function get_share_slug_map( ?array $slug_sources = null ): array {
        $slug_sources   = is_array( $slug_sources ) ? $slug_sources : $this->get_share_slug_sources();
        $assigned_slugs = [];
        $occupied_slugs = [];
        $grouped_forms  = [];

        foreach ( $slug_sources as $slug_source ) {
            $form_id = isset( $slug_source->id ) ? intval( $slug_source->id ) : 0;

            if ( $form_id <= 0 ) {
                continue;
            }

            $persisted_slug = $this->get_source_share_slug( $slug_source );

            if ( '' !== $persisted_slug ) {
                $assigned_slugs[ $form_id ]        = $persisted_slug;
                $occupied_slugs[ $persisted_slug ] = $form_id;
                continue;
            }

            $base_slug = $this->normalize_share_slug(
                (string) ( $slug_source->title ?? '' ),
                $form_id
            );

            if ( ! isset( $grouped_forms[ $base_slug ] ) ) {
                $grouped_forms[ $base_slug ] = [];
            }

            $grouped_forms[ $base_slug ][] = $slug_source;
        }

        foreach ( $grouped_forms as $base_slug => $group_forms ) {
            usort(
                $group_forms,
                function( $left, $right ) {
                    return intval( $left->id ?? 0 ) <=> intval( $right->id ?? 0 );
                }
            );

            $primary_form_id = intval( $group_forms[0]->id ?? 0 );

            foreach ( $group_forms as $group_form ) {
                $form_id = intval( $group_form->id ?? 0 );

                if ( $form_id <= 0 ) {
                    continue;
                }

                $candidate_slug = $form_id === $primary_form_id &&
                    ! isset( $occupied_slugs[ $base_slug ] )
                        ? $base_slug
                        : "{$base_slug}_{$form_id}";

                $candidate_base = $candidate_slug;
                $suffix         = 2;

                while ( isset( $occupied_slugs[ $candidate_slug ] ) &&
                    intval( $occupied_slugs[ $candidate_slug ] ) !== $form_id
                ) {
                    $candidate_slug = "{$candidate_base}_{$suffix}";
                    $suffix++;
                }

                $assigned_slugs[ $form_id ]        = $candidate_slug;
                $occupied_slugs[ $candidate_slug ] = $form_id;
            }
        }

        return $assigned_slugs;
    }

    protected function ensure_share_slug_map( ?array $slug_sources = null ): array {
        $slug_sources = is_array( $slug_sources ) ? $slug_sources : $this->get_share_slug_sources();
        $slug_map     = $this->get_share_slug_map( $slug_sources );

        foreach ( $slug_sources as $slug_source ) {
            $form_id = isset( $slug_source->id ) ? intval( $slug_source->id ) : 0;

            if ( $form_id <= 0 || empty( $slug_map[ $form_id ] ) ) {
                continue;
            }

            $existing_slug = $this->get_source_share_slug( $slug_source );

            if ( $existing_slug === $slug_map[ $form_id ] ) {
                continue;
            }

            $this->update_meta(
                $form_id,
                self::SHARESLUGMETAKEY,
                $slug_map[ $form_id ]
            );
            $slug_source->share_slug = $slug_map[ $form_id ];
        }

        return $slug_map;
    }

    public function get_share_slug( stdClass $form, ?array $slug_sources = null ): string {
        $form_id   = isset( $form->id ) ? intval( $form->id ) : 0;
        $base_slug = $this->normalize_share_slug( (string) ( $form->title ?? '' ), $form_id );

        if ( 0 === $form_id ) {
            return $base_slug;
        }

        $persisted_slug = $this->get_source_share_slug( $form );

        if ( '' !== $persisted_slug ) {
            return $persisted_slug;
        }

        $slug_sources = is_array( $slug_sources ) ? $slug_sources : $this->get_share_slug_sources();
        $slug_map     = $this->ensure_share_slug_map( $slug_sources );

        if ( ! empty( $slug_map[ $form_id ] ) ) {
            return $slug_map[ $form_id ];
        }

        return $base_slug;
    }

    public function get_share_url( stdClass $form, ?array $slug_sources = null ): string {
        if ( 'publish' !== (string) ( $form->status ?? '' ) ) {
            return '';
        }

        return get_site_url( null, 'form/' . $this->get_share_slug( $form, $slug_sources ) );
    }

    public function get_by_share_slug_publish( string $slug ) {
        $slug_sources = $this->get_share_slug_sources( 'publish' );
        $slug_map     = $this->ensure_share_slug_map( $slug_sources );
        $slug         = $this->normalize_share_slug( $slug );

        foreach ( $slug_sources as $slug_source ) {
            $form_id = intval( $slug_source->id ?? 0 );

            if ( empty( $slug_map[ $form_id ] ) || $slug_map[ $form_id ] !== $slug ) {
                continue;
            }

            return $this->get_by_id_publish( $form_id );
        }

        return null;
    }

    public function get( int $per_page, int $page, string $search, string $sort, string $status, string $date_type = 'all', array $date_frame = [], string $type = 'admin_forms' ) {
        $forms_query = Form::query( "form" )->left_join( "users as user", "user.ID", "=", "form.created_by" );

        /**
         * Search forms
         */
        if ( ! empty( $search ) ) {
            global $wpdb;
            $search       = "%{$search}%";
            $search_query = $wpdb->prepare( "(form.title like %s or user.user_nicename like %s)", $search, $search );
            $forms_query->where_raw( $search_query );
        }

        /**
         * Filter by date
         */
        if ( 'all' !== $date_type ) {
            $this->forms_date_query( $forms_query, $date_type, $date_frame );
        }

        $forms_count_query = clone $forms_query;

        if ( ! empty( $status ) && 'all' !== $status ) {
            $forms_query->where( 'form.status', $status );
        }

        $columns = [
            'form.id', 
            'form.title', 
            'form.status', 
            'form.created_at', 
            'form.updated_at', 
            'form.content', 
            'user.user_nicename as username',
            "SUM( CASE WHEN
                (
                    message.is_read = 0 AND 
                    (
                        message.created_by = 0 
                        OR message_user.registered_id IS NULL 
                        OR ( message_user_meta.meta_value NOT LIKE '%administrator%' AND message_user_meta.meta_value NOT LIKE '%helpgent_agent%' ) 
                    )
                ) OR 
                (
                    chat_message.is_read = 0 AND 
                    (
                        chat_message.created_by = 0 
                        OR chat_message_user.registered_id IS NULL 
                        OR ( chat_message_user_meta.meta_value NOT LIKE '%administrator%' AND chat_message_user_meta.meta_value NOT LIKE '%helpgent_agent%' ) 
                    )
                )
                THEN 1 ELSE 0 END 
            ) AS total_unread
            ",
        ];
        
        $group_by_columns = ['form.id', 'form.title', 'form.status', 'form.created_at', 'form.updated_at', 'form.content', 'user.user_nicename'];

        $forms_query->
            select( $columns )
            ->with_count(
                'responses as total_responses', function( Builder $query ) {
                    $query->where_raw( "{$query->as}.is_archive = 0" );
                }
            )
            ->left_join( 'helpgent_responses as response', 'form.id', '=', 'response.form_id' )
            ->left_join( 'helpgent_messages as message', 'message.response_id', '=', 'response.id' )
            ->left_join( 'helpgent_chat_messages as chat_message', 'chat_message.response_id', '=', 'response.id' )
            ->left_join( 'helpgent_users as message_user', 'message_user.id', '=', 'message.created_by' )
            ->left_join(
                'usermeta as message_user_meta', function( $join ) {
                    global $wpdb;

                    $join
                    ->on_column( 'message_user.registered_id', '=', 'message_user_meta.user_id' )
                    ->on_column( 'message_user_meta.meta_key', '=', "'{$wpdb->prefix}capabilities'" );
                } 
            )
            ->left_join( 'helpgent_users as chat_message_user', 'chat_message_user.id', '=', 'chat_message.created_by' )
            ->left_join(
                'usermeta as chat_message_user_meta', function( $join ) {
                    global $wpdb;

                    $join
                    ->on_column( 'chat_message_user.registered_id', '=', 'chat_message_user_meta.user_id' )
                    ->on_column( 'chat_message_user_meta.meta_key', '=', "'{$wpdb->prefix}capabilities'" );
                } 
            )
            ->group_by( $group_by_columns );

        if ( ! empty( $sort ) ) {
            switch ( $sort ) {
                case 'last_modified':
                    $forms_query->order_by_desc( 'form.updated_at' );
                    break;
                case 'alphabetical':
                    $forms_query->order_by_raw( 'form.title asc, form.id desc' );
                    break;
                case 'last_submission':
                    $forms_query->left_join(
                        'helpgent_responses as max_response', function( JoinClause $join ) {
                            $join->select( 'max_response.form_id', 'MAX(max_response.id) AS max_id' )->on_column( 'response.form_id', 'max_response.form_id' )->on_column( 'response.id', 'max_response.max_id' );
                        }
                    )->order_by_desc( 'response.id' );
                    break;
                case 'unread':
                    $forms_query->order_by( 'total_unread', 'desc' );
                    break;
                case 'date_created':
                default:
                    $forms_query->order_by_desc( 'form.id' );
            }
        } else {
            $forms_query->order_by_desc( 'form.id' );
        }
 
        do_action( 'helpgent_form_query', $forms_query, $type );

        $join_form_meta = apply_filters( 'helpgent_form_query_join_meta', false );

        /**
         * Join form meta if joining allowed form filter hook
         */
        if ( $join_form_meta ) {
            $forms_query->left_join( "helpgent_form_meta as form_meta", "form_meta.form_id", "=", "form.id" );
            $forms_count_query->left_join( "helpgent_form_meta as form_meta", "form_meta.form_id", "=", "form.id" );
        }

        do_action( 'helpgent_form_count_query', $forms_count_query, $type );

        $preview_url  = get_site_url( null, "helpgent/" );
        $slug_sources = $this->get_share_slug_sources();
        $slug_map     = $this->ensure_share_slug_map( $slug_sources );
        $forms        = array_map(
            function( $form ) use( $preview_url, $slug_map ) {
                $form = Repository::bind_form_featured_image( $form );
                unset( $form->content );

                $form->preview_url = $preview_url . $form->id;
                $form->share_url   = 'publish' === (string) ( $form->status ?? '' )
                    ? get_site_url(
                        null,
                        'form/' . ( $slug_map[ intval( $form->id ) ] ?? $this->normalize_share_slug( (string) ( $form->title ?? '' ), intval( $form->id ) ) )
                    )
                    : '';
                return $form;
            }, $forms_query->pagination( $page, $per_page )
        );

        if ( ! empty( $status ) && 'all' !== $status ) {
            $status_total_query = clone $forms_count_query;

            $status_total = $status_total_query->select( 'form.status', 'COUNT(DISTINCT form.id) as total' )->group_by( 'form.status' )->get();
            $total        = $forms_count_query->where( 'form.status', $status )->count( 'form.id' );
        } else {
            $status_total = $forms_count_query->select( 'form.status', 'COUNT(DISTINCT form.id) as total' )->group_by( 'form.status' )->get();
            $total        = array_sum( array_column( $status_total, 'total' ) );
        }

        return apply_filters(
            'helpgent_get_forms', [
                'total'         => $total,
                'status_totals' => $status_total,
                'forms'         => $forms
            ], $type
        );
    }

    protected function forms_date_query( Builder $query, string $date_type, array $date_frame ) {
        $now              = helpgent_now();
        $from_date_format = "Y-m-d 00:00:01";
        $to_date_format   = "Y-m-d 23:59:59";

        if ( 'today' === $date_type ) {
            $today = $now->format( $from_date_format );
            return $query->where_raw( "((form.updated_at is not null and form.updated_at > '{$today}') or (form.updated_at is null and form.created_at > '{$today}'))" );
        }

        if ( 'date_frame' === $date_type ) {
            /**
             * Checking if the date is valid and the date format is correct.
             */
            if ( empty( $date_frame['from'] ) ||
                ! is_string( $date_frame['from'] ) ||
                empty( $date_frame['to'] ) || 
                ! is_string( $date_frame['to'] ) ||
                ! helpgent_is_valid_date( $date_frame['from'], $from_date_format ) ||
                ! helpgent_is_valid_date( $date_frame['to'], $to_date_format )
            ) {
                return $query;
            }

            $form = sanitize_text_field( $date_frame['from'] );
            $to   = sanitize_text_field( $date_frame['to'] );
        } else {
            switch ( $date_type ) {
                case 'yesterday':
                    $date = $now->sub_days( 1 );
                    break;
                case 'last_week':
                    $date = $now->sub_days( 6 );
                    break;
                case 'last_month':
                    $date = $now->sub_days( 30 );
            }
            $form = $date->format( $from_date_format );
            $to   = helpgent_now()->format( $to_date_format );
        }

        return $query->where_raw( "((form.updated_at is not null and form.updated_at > '{$form}' and form.updated_at < '{$to}') or (form.updated_at is null and form.created_at > '{$form}' and form.created_at < '{$to}'))" );
    }

    public function select_list( int $per_page, int $page, string $search = '', $ids = [] ) {
        $query = Form::query( 'form' );

        if ( ! empty( $ids ) ) {
            $query->where_in( 'id', $ids );
        } elseif ( ! empty( $search ) ) {
            $query->where( 'title', 'like', "%{$search}%" );
        }

        $columns = ['form.id', 'form.title'];

        $query->select( $columns )->group_by( $columns )->order_by_desc( 'form.id' );

        do_action( 'helpgent_form_select_list_query', $query, $per_page, $page, $search, $ids );

        $join_form_meta = apply_filters( 'helpgent_form_select_query_join_meta', false );

        if ( $join_form_meta ) {
            $query->left_join( "helpgent_form_meta as form_meta", "form_meta.form_id", "=", "form.id" );
        }

        return $query->pagination( $page, $per_page );
    }

    public function get_single( int $id ) {
        $columns = ['form.id', 'form.title', 'form.status','form.content', 'form.draft', 'form.is_chat_bubble', 'form.available_pages'];

        $query = Form::query( 'form' )->select( $columns )->where( 'form.id', $id )->group_by( $columns );

        /**
         * @var Builder $query
         */
        $query          = apply_filters( 'helpgent_single_form_query', $query );
        $join_form_meta = apply_filters( 'helpgent_single_form_query_join_meta', false );

        if ( $join_form_meta ) {
            $query->left_join( "helpgent_form_meta as form_meta", "form_meta.form_id", "=", "form.id" );
        }

        $form = $query->first();

        if ( ! $form ) {
            throw new Exception( esc_html__( 'Form not found.', 'helpgent' ), 404 );
        }

        if ( ! empty( $form->draft ) ) {
            $draft          = json_decode( $form->draft );
            $draft->content = wp_json_encode( $draft->content );
            $form->draft    = wp_json_encode( helpgent_migrate_old_form( $draft ) );
        }

        $form->preview_url     = get_site_url( null, "helpgent/{$form->id}" );
        $form->share_url       = $this->get_share_url( $form );
        $form->available_pages = json_decode( $form->available_pages );
        $form                  = helpgent_migrate_old_form( $form );

        return apply_filters( 'helpgent_form_with_builder_media_assets', $form );
    }

    public function create( FormDTO $form_dto ) {
        $form_id = Form::query()->insert_get_id(
            [
                'title'           => $form_dto->get_title(),
                'status'          => $form_dto->get_status(),
                'content'         => $form_dto->get_content(),
                'font_family'     => $this->get_font_family( $form_dto ),
                'is_chat_bubble'  => $form_dto->get_is_chat_bubble(),
                'available_pages' => wp_json_encode( $form_dto->get_available_pages() ),
                'created_by'      => $form_dto->get_created_by(),
                'created_at'      => helpgent_now(),
            ]
        );

        $this->add_meta( $form_id, 'color', 'rgb(' . wp_rand( 0, 255 ) . ',' . wp_rand( 0, 255 ) . ',' . wp_rand( 0, 255 ) . ')' );
        $this->ensure_share_slug_map( $this->get_share_slug_sources() );

        return $form_id;
    }

    public function update( FormDTO $form_dto ) {
        $form = $this->get_by_id( $form_dto->get_id() );

        if ( ! $form ) {
            throw new Exception( esc_html__( 'Form not found.', 'helpgent' ), 404 );
        }

        return Form::query()->where( 'id', $form_dto->get_id() )->update(
            [
                'title'           => $form_dto->get_title(),
                'status'          => $form_dto->get_status(),
                'content'         => $form_dto->get_content(),
                'draft'           => $form_dto->get_draft(),
                'is_chat_bubble'  => $form_dto->get_is_chat_bubble(),
                'font_family'     => $this->get_font_family( $form_dto ),
                'available_pages' => wp_json_encode( $form_dto->get_available_pages() ),
                'updated_at'      => helpgent_now()
            ]
        );
    }

    protected function get_font_family( FormDTO $form_dto ) {
        $form_content = json_decode( $form_dto->get_content(), true );
        return ! empty( $form_content['global']['font_family']['family'] ) ? $form_content['global']['font_family']['family'] : null;
    }

    public function update_status( int $id, string $status ) {
        $form = $this->get_by_id( $id );

        if ( ! $form ) {
            throw new Exception( esc_html__( 'Form not found.', 'helpgent' ), 404 );
        }

        return Form::query()->where( 'id', $id )->update(
            [
                'status'     => $status,
                'updated_at' => helpgent_now()
            ]
        );
    }

    public function update_title( int $id, string $title ) {
        $form = $this->get_by_id( $id );

        if ( ! $form ) {
            throw new Exception( esc_html__( 'Form not found.', 'helpgent' ), 404 );
        }

        return Form::query()->where( 'id', $id )->update(
            [
                'title'      => $title,
                'updated_at' => helpgent_now(),
            ]
        );
    }

    public function update_content( int $id, string $content ) {
        return Form::query()->where( 'id', $id )->update(
            [
                'content'    => $content,
                'updated_at' => helpgent_now(),
            ]
        );
    }
    
    public function update_draft( int $id, string $content ) {
        return Form::query()->where( 'id', $id )->update(
            [
                'draft'      => $content,
                'updated_at' => helpgent_now(),
            ]
        );
    }

    public function get_export_payload( int $id ): array {
        $form = $this->get_by_id( $id );

        if ( ! $form ) {
            throw new Exception( esc_html__( 'Form not found.', 'helpgent' ), 404 );
        }

        $meta       = $this->get_exportable_meta( $id );
        $share_slug = ! empty( $meta[ self::SHARESLUGMETAKEY ] ) && is_string( $meta[ self::SHARESLUGMETAKEY ] )
            ? $meta[ self::SHARESLUGMETAKEY ]
            : $this->get_share_slug( $form );

        return apply_filters(
            'helpgent_form_export_payload',
            [
                'type'        => 'helpgent_form_export',
                'version'     => '1.0.0',
                'exported_at' => gmdate( 'c' ),
                'source'      => [
                    'site_url'       => get_site_url(),
                    'plugin_version' => defined( 'HELPGENT_DEPENDENCY_VERSION' ) ? HELPGENT_DEPENDENCY_VERSION : '',
                ],
                'form'        => [
                    'title'           => (string) ( $form->title ?? '' ),
                    'status'          => (string) ( $form->status ?? 'draft' ),
                    'content'         => $this->decode_required_json_value( $form->content ?? '', 'content' ),
                    'draft'           => $this->decode_optional_json_value( $form->draft ?? null, 'draft' ),
                    'font_family'     => (string) ( $form->font_family ?? '' ),
                    'is_chat_bubble'  => intval( $form->is_chat_bubble ?? 0 ),
                    'available_pages' => $this->normalize_import_available_pages( $form->available_pages ?? [] ),
                ],
                'settings'    => [
                    'share' => [
                        'slug' => $share_slug,
                    ],
                    'meta'  => $meta,
                ],
                'media'       => [
                    'mode' => 'reference',
                    'note' => 'Media files are referenced by their stored URLs and attachment IDs. The files themselves are not bundled in this JSON export.',
                ],
            ],
            $form,
            $id
        );
    }

    public function import_export_payload( array $payload, int $created_by ): int {
        $payload      = $this->normalize_import_payload( $payload );
        $form_payload = $payload['form'];
        $title        = $this->get_import_title( $form_payload['title'] ?? '' );
        $content      = $this->normalize_import_content( $form_payload['content'] ?? null );

        $available_pages = $this->normalize_import_available_pages(
            $form_payload['available_pages'] ?? ( $content['global']['bubble_pages'] ?? [] )
        );
        $is_chat_bubble  = $this->normalize_import_chat_bubble_value(
            $form_payload['is_chat_bubble'] ?? ( $content['global']['chat_bubble'] ?? 0 )
        );

        $dto = ( new FormDTO() )
            ->set_title( $title )
            ->set_status( 'draft' )
            ->set_content( wp_json_encode( $content ) )
            ->set_is_chat_bubble( $is_chat_bubble )
            ->set_available_pages( $available_pages )
            ->set_created_by( $created_by );

        $form_id = $this->create( $dto );
        $draft   = $this->normalize_import_draft(
            $form_payload['draft'] ?? null,
            $form_id,
            $title,
            $content,
            $is_chat_bubble,
            $available_pages
        );

        if ( null !== $draft ) {
            $this->update_draft( $form_id, wp_json_encode( $draft ) );
        }

        $this->import_exported_meta( $payload, $form_id, $title );

        return $form_id;
    }

    protected function get_exportable_meta( int $form_id ): array {
        $metas      = FormMeta::query()->where( 'form_id', $form_id )->get();
        $exportable = [];

        foreach ( $metas as $meta ) {
            $meta_key = (string) ( $meta->meta_key ?? '' );

            if ( ! $this->is_exportable_meta_key( $meta_key ) ) {
                continue;
            }

            $exportable[ $meta_key ] = $this->maybe_decode_json_value( $meta->meta_value ?? '' );
        }

        return $exportable;
    }

    protected function is_exportable_meta_key( string $meta_key ): bool {
        if ( '' === $meta_key ) {
            return false;
        }

        if ( 0 === strpos( $meta_key, 'response_token-' ) ) {
            return false;
        }

        return ! in_array( $meta_key, [ 'access_lock', 'last_access_lock' ], true );
    }

    protected function normalize_import_payload( array $payload ): array {
        if ( isset( $payload['type'] ) &&
            '' !== (string) $payload['type'] &&
            'helpgent_form_export' !== (string) $payload['type']
        ) {
            throw new Exception( esc_html__( 'Please select a valid HelpGent form export file.', 'helpgent' ), 422 );
        }

        if ( ! empty( $payload['form'] ) && is_array( $payload['form'] ) ) {
            return $payload;
        }

        if ( ! empty( $payload['content'] ) ) {
            return [
                'form'     => $payload,
                'settings' => $payload['settings'] ?? [],
            ];
        }

        throw new Exception( esc_html__( 'Please select a valid HelpGent form export file.', 'helpgent' ), 422 );
    }

    protected function normalize_import_content( $content ): array {
        $content = $this->decode_required_json_value( $content, 'content' );

        if ( empty( $content['elements'] ) || ! is_array( $content['elements'] ) ) {
            throw new Exception( esc_html__( 'The selected file does not contain valid form elements.', 'helpgent' ), 422 );
        }

        if ( empty( $content['global'] ) || ! is_array( $content['global'] ) ) {
            $content['global'] = [];
        }

        return $content;
    }

    protected function normalize_import_draft( $draft, int $form_id, string $title, array $fallback_content, int $is_chat_bubble, array $available_pages ): ?array {
        $draft = $this->decode_optional_json_value( $draft, 'draft' );

        if ( null === $draft ) {
            return null;
        }

        $draft['content']         = isset( $draft['content'] )
            ? $this->normalize_import_content( $draft['content'] )
            : $fallback_content;
        $draft['id']              = (string) $form_id;
        $draft['title']           = $title;
        $draft['status']          = 'draft';
        $draft['is_chat_bubble']  = $is_chat_bubble;
        $draft['available_pages'] = $available_pages;

        return $draft;
    }

    protected function import_exported_meta( array $payload, int $form_id, string $title ): void {
        $settings = ! empty( $payload['settings'] ) && is_array( $payload['settings'] ) ? $payload['settings'] : [];
        $meta     = ! empty( $settings['meta'] ) && is_array( $settings['meta'] ) ? $settings['meta'] : [];

        if ( empty( $meta[ self::SHARESLUGMETAKEY ] ) && ! empty( $settings['share']['slug'] ) ) {
            $meta[ self::SHARESLUGMETAKEY ] = $settings['share']['slug'];
        }

        foreach ( $meta as $meta_key => $meta_value ) {
            $meta_key = (string) $meta_key;

            if ( ! $this->is_exportable_meta_key( $meta_key ) ) {
                continue;
            }

            $meta_value = is_array( $meta_value ) || is_object( $meta_value )
                ? wp_json_encode( $meta_value )
                : (string) $meta_value;

            if ( self::SHARESLUGMETAKEY === $meta_key ) {
                $meta_value = $this->get_unique_share_slug( $meta_value, $form_id, $title );
            }

            $this->update_meta( $form_id, $meta_key, $meta_value );
        }
    }

    protected function get_import_title( $title ): string {
        $title = sanitize_text_field( (string) $title );
        $title = trim( $title );

        if ( strlen( $title ) < 5 ) {
            return esc_html__( 'Imported form', 'helpgent' );
        }

        /* translators: %s: imported form title. */
        return sprintf( esc_html__( '%s imported', 'helpgent' ), $title );
    }

    protected function normalize_import_available_pages( $available_pages ): array {
        $available_pages = $this->decode_optional_json_value( $available_pages, 'available_pages' );

        if ( ! is_array( $available_pages ) ) {
            return [];
        }

        return array_values(
            array_filter(
                $available_pages,
                function( $page ) {
                    return is_scalar( $page );
                }
            )
        );
    }

    protected function normalize_import_chat_bubble_value( $value ): int {
        return in_array( intval( $value ), [ 0, 1 ], true ) ? intval( $value ) : 0;
    }

    protected function decode_required_json_value( $value, string $field ): array {
        $decoded = $this->decode_optional_json_value( $value, $field );

        if ( is_array( $decoded ) ) {
            return $decoded;
        }

        /* translators: %s: exported form field name. */
        throw new Exception( sprintf( esc_html__( 'The exported form %s is invalid.', 'helpgent' ), esc_html( $field ) ), 422 );
    }

    protected function decode_optional_json_value( $value, string $field ): ?array {
        if ( is_array( $value ) ) {
            return $value;
        }

        if ( is_object( $value ) ) {
            $decoded = json_decode( wp_json_encode( $value ), true );

            return is_array( $decoded ) ? $decoded : null;
        }

        if ( null === $value || '' === trim( (string) $value ) ) {
            return null;
        }

        $decoded = json_decode( (string) $value, true );

        if ( JSON_ERROR_NONE === json_last_error() && is_array( $decoded ) ) {
            return $decoded;
        }

        /* translators: %s: exported form field name. */
        throw new Exception( sprintf( esc_html__( 'The exported form %s is invalid.', 'helpgent' ), esc_html( $field ) ), 422 );
    }

    protected function maybe_decode_json_value( $value ) {
        if ( ! is_string( $value ) || '' === trim( $value ) ) {
            return $value;
        }

        $decoded = json_decode( $value, true );

        if ( JSON_ERROR_NONE === json_last_error() ) {
            return $decoded;
        }

        return $value;
    }

    protected function get_unique_share_slug( string $preferred_slug, int $form_id, string $fallback_title ): string {
        $base_slug  = $this->normalize_share_slug( '' !== $preferred_slug ? $preferred_slug : $fallback_title, $form_id );
        $slug       = $base_slug;
        $suffix     = 2;
        $slug_map   = $this->get_share_slug_map( $this->get_share_slug_sources() );

        while ( $this->share_slug_exists_in_map( $slug, $form_id, $slug_map ) ) {
            $slug = "{$base_slug}_{$suffix}";
            $suffix++;
        }

        return $slug;
    }

    protected function share_slug_exists_in_map( string $slug, int $form_id, array $slug_map ): bool {
        foreach ( $slug_map as $source_form_id => $source_slug ) {
            if ( intval( $source_form_id ) === $form_id ) {
                continue;
            }

            if ( $source_slug === $slug ) {
                return true;
            }
        }

        return false;
    }

    public function delete( int $id ) {
        $form = $this->get_by_id( $id );

        if ( ! $form ) {
            throw new Exception( esc_html__( 'Form not found.', 'helpgent' ), 404 );
        }

        Form::query()->where( 'id', $id )->delete();

        $this->after_delete_form();

        return $form;
    }

    public function deletes( array $ids ) {
        $forms = $this->get_by_ids( $ids );

        if ( ! $forms ) {
            throw new Exception( esc_html__( 'Forms not found.', 'helpgent' ), 404 );
        }

        Form::query()->where_in( 'id', $ids )->delete();

        $this->after_delete_form();

        return $forms;
    }

    public function update_bulk_status( array $ids, string $status ) {
        return Form::query()->where_in( 'id', $ids )->update(
            [
                'status' => $status
            ]
        );
    }

    protected function after_delete_form() {
        Repository::delete_unused_forward_messages();
    }

    public function get_by_id( int $id ) {
        return Form::query()->where( 'id', $id )->first();
    }

    public function get_by_ids( array $ids ) {
        return Form::query()->where_in( 'id', $ids )->get();
    }

    public function get_by_id_publish( int $id ) {
        $form = Form::query()->where( 'id', $id )->where( 'status', 'publish' )->first();

        if ( ! $form ) {
            return null;
        }

        return apply_filters( 'helpgent_form_with_builder_media_assets', helpgent_migrate_old_form( $form ) );
    }

    public function get_bubble_by_page_id( int $page_id ) {
        $columns = ['form.id', 'form.content'];
        $query   = Form::query( 'form' )->select( $columns )->where( 'form.status', 'publish' )->where( 'is_chat_bubble', 1 )->where(
            function( $query ) use( $page_id ) {
                $query->where( "JSON_LENGTH(form.available_pages)", 0 );                
                $front_page_id = get_option( 'page_on_front' );

                if ( $page_id == $front_page_id ) {
                    $query->or_where(
                        function( $query ) use( $page_id ) {
                            $query->where_raw( "JSON_CONTAINS(form.available_pages, JSON_QUOTE('0'))" );
                            $query->or_where_raw( "JSON_CONTAINS(form.available_pages, JSON_QUOTE('{$page_id}'))" );
                        }
                    );
                } else {
                    $query->or_where_raw( "JSON_CONTAINS(form.available_pages, JSON_QUOTE('{$page_id}'))" );
                }
            }
        )->group_by( $columns );

        /**
         * @var Builder $query
         */
        $query     = apply_filters( 'helpgent_chat_bubble_form_query', $query );
        $join_meta = apply_filters( 'helpgent_chat_bubble_form_query_join_meta', false );

        if ( $join_meta ) {
            $query->left_join( "helpgent_form_meta as form_meta", "form_meta.form_id", "=", "form.id" );
        }

        return array_map(
            function( $form ) {
                return apply_filters( 'helpgent_form_with_builder_media_assets', helpgent_migrate_old_form( $form ) );
            },
            $query->get()
        );
    }

    public function get_response_token( int $response_id, int $form_id ): string {
        $meta_data = FormMeta::query()
            ->where( 'form_id', $form_id )
            ->where( 'meta_value', $response_id )
            ->where_like( 'meta_key', '%response_token-%' )
            ->first();

        if ( empty( $meta_data ) ) {
            return '';
        }

        return $meta_data->meta_key;
    }

    public function get_meta( int $form_id, string $meta_key ) {
        return FormMeta::query()->where( 'form_id', $form_id )->where( 'meta_key', $meta_key )->first();
    }

    public function get_meta_value( int $form_id, string $meta_key ) {
        $meta = $this->get_meta( $form_id, $meta_key );
        if ( ! $meta ) {
            return false;
        }
        return $meta->meta_value;
    }

    public function add_meta( int $form_id, string $meta_key, string $meta_value ) {
        $meta = $this->get_meta( $form_id, $meta_key );

        if ( $meta ) {
            return false;
        }

        return FormMeta::query()->insert(
            [
                'form_id'    => $form_id,
                'meta_key'   => $meta_key,
                'meta_value' => $meta_value,
            ]
        );
    }

    public function update_meta( int $form_id, string $meta_key, string $meta_value ) {

        $update = FormMeta::query()->where( 'form_id', $form_id )->where( 'meta_key', $meta_key )->update(
            [
                'meta_value' => $meta_value,
            ]
        );

        if ( $update ) {
            return $update;
        }

        return FormMeta::query()->insert(
            [
                'form_id'    => $form_id,
                'meta_key'   => $meta_key,
                'meta_value' => $meta_value,
            ]
        );
    }

    public function delete_meta( int $form_id, string $meta_key ) {
        return FormMeta::query()->where( 'form_id', $form_id )->where( 'meta_key', $meta_key )->delete();
    }

    public function insert_media( string $attachment_url ) {
        $demo_attachments = $this->get_demo_attachments();

        /**
         * Return attachment if from cache if exists
         */
        if ( ! empty( $demo_attachments[$attachment_url] ) ) {
            $id         = $demo_attachments[$attachment_url];
            $attachment = wp_get_attachment_url( $id, );

            if ( is_string( $attachment_url ) ) {
                return [
                    'id'  => $id,
                    'url' => $attachment_url
                ];
            }
        }

        $response = wp_remote_get(
            $attachment_url, [
                [
                    'timeout' => 30
                ]
            ]
        );

        if ( is_wp_error( $response ) ) {
            $error_code    = $response->get_error_code();
            $response_code = 500;

            if ( is_string( $error_code ) ) {
                if ( 'http_request_failed' === $error_code ) {
                    $response_code = 495;
                }
            } else {
                $response_code = $error_code;
            }
            throw new Exception( esc_html( $response->get_error_message() ), esc_attr( $response_code ) );
        }

        $response_code = intval( wp_remote_retrieve_response_code( $response ) );

        if ( 200 !== $response_code ) {
            throw new Exception( esc_html( wp_remote_retrieve_response_message( $response ) ), esc_attr( $response_code ) );
        }

        $file_name = wp_basename( $attachment_url );
        $upload    = wp_upload_bits( $file_name, null, wp_remote_retrieve_body( $response ) );

        if ( ! empty( $upload['error'] ) ) {
            throw new Exception( esc_html( $upload['error'] ), 500 );
        }

        $attachment = [
            'post_title'     => $file_name,
            'post_type'      => 'attachment',
            'post_mime_type' => $upload['type'],
            'guid'           => $upload['url']
        ];

        $id = wp_insert_attachment( $attachment, $upload['file'] );

        if ( is_wp_error( $id ) ) {
            throw new Exception( esc_html( $id->get_error_message() ), esc_attr( $id->get_error_code() ) );
        }

        if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
            include_once ABSPATH . 'wp-admin/includes/image.php';
        }

        if ( ! function_exists( 'wp_read_video_metadata' ) ) {
            include_once ABSPATH . 'wp-admin/includes/media.php';
        }

        wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) );

        /**
         * Caching the inserted attachment url and id
         */
        $demo_attachments[$attachment_url] = $id;

        $this->update_demo_attachments( $demo_attachments );

        return [
            'id'  => $id,
            'url' => $upload['url']
        ];
    }

    public function get_demo_attachments():array {
        return get_option( self::DEMOMEDIAOPTIONKEY, [] );
    }

    public function update_demo_attachments( array $demo_attachments ) {
        return update_option( self::DEMOMEDIAOPTIONKEY, $demo_attachments );
    }

    public function delete_demo_attachment_by_id( int $attachment_id ) : void {
        $attachments = array_filter(
            $this->get_demo_attachments(), function ( $value ) use ( $attachment_id ) {
                return $value !== $attachment_id;
            }
        );

        $this->update_demo_attachments( $attachments );
    }

    /**
     * Determines whether any form is currently being accessed by another user.
     *
     * @since 3.0.0
     */
    public function check_has_any_access_lock( int $user_id, string $session_token ): array {
        $locks = FormMeta::query()
            ->where( 'meta_key', 'access_lock' )
            ->get();
        
        foreach ( $locks as $lock_data ) {
            $status = $this->access_lock_status( $user_id, $session_token, $lock_data );

            if ( $status['is_locked'] ) {
                return $status;
            }
        }

        return [ 
            'is_locked'   => false,
            'status_code' => 'unlocked',
        ];
    }

    /**
     * Determines whether the form is currently being accessed by another user.
     *
     * @since 3.0.0
     */
    public function check_access_lock( int $form_id, int $user_id, string $session_token ): array {
        $lock_data = FormMeta::query()
            ->where( 'form_id', $form_id )
            ->where( 'meta_key', 'access_lock' )
            ->first();

        if ( ! $lock_data ) {
            return [ 
                'is_locked'   => false,
                'status_code' => 'unlocked',
            ];
        }

        return $this->access_lock_status( $user_id, $session_token, $lock_data );
    }

    private function access_lock_status( int $user_id, string $session_token, stdClass $data ): array {
        $lock = explode( ':', $data->meta_value );

        if ( count( $lock ) < 3 ) {
            return [ 
                'is_locked'   => false,
                'status_code' => 'invalid_lock_format',
            ];
        }

        $form_id            = $data->form_id;
        $lock_time          = absint( $lock[0] );
        $lock_user_id       = absint( $lock[1] );
        $lock_session_token = $lock[2];

        $time_window = apply_filters( 'helpgent_check_form_lock_window', 150 );

        // Check if the lock is expaired.
        if ( $lock_time < time() - $time_window ) {
            $this->remove_access_lock( $form_id );

            return [
                'is_locked'   => false,
                'status_code' => 'expaired',
            ];
        }

        // If the form is not locked to the user then return the ID of the user with lock.
        if ( $lock_user_id !== $user_id ) {
            $last_lock = $this->get_last_access_lock( $form_id );

            return [
                'is_locked'               => true,
                'locked_by_user_id'       => $lock_user_id,
                'locked_by_session_token' => $lock_session_token,
                'status_code'             => $last_lock && $last_lock['user_id'] === $user_id ? 'taken_over_by_user' : 'locked_by_user',
            ];
        }

        if ( $lock_session_token !== $session_token ) {
            $session_manager     = WP_Session_Tokens::get_instance( $lock_user_id );
            $locked_session_data = $session_manager->get( $session_token );
            $last_lock           = $this->get_last_access_lock( $form_id );

            return [
                'is_locked'               => true,
                'locked_by_user_id'       => $lock_user_id,
                'locked_by_session_token' => $lock_session_token,
                'locked_by_device'        => isset( $locked_session_data['ua'] ) ? $locked_session_data['ua'] : __( 'Unknown', 'helpgent' ),
                'status_code'             => $last_lock && $last_lock['session_token'] === $session_token ? 'taken_over_by_device' : 'locked_by_device',
            ];
        }

        return [
            'is_locked'   => false,
            'status_code' => 'authorized',
        ];
    }

    /**
     * Marks the form as currently being accessed by the current user.
     *
     * @since 3.0.0
     *
     * @param int $form ID of the form being accesed.
     * @param int $user_id ID of the user to lock with the form.
     * @param string $session_token The session token of the user to lock with the form.
     * @return ?string Lock token on success or null on fail.
     */
    public function set_access_lock( int $form_id, $user_id, string $session_token ): ?string {
        $form = $this->get_by_id( $form_id );

        if ( ! $form ) {
            return null;
        }
        
        if ( 0 === $user_id ) {
            return null;
        }

        $session_token = wp_get_session_token();
        $now           = time();
        $lock          = "{$now}:{$user_id}:{$session_token}";

        $old_form_lock = FormMeta::query()
            ->where( 'form_id', $form_id )
            ->where( 'meta_key', 'access_lock' )
            ->first();

        if ( $old_form_lock ) {
            $status = FormMeta::query()
                ->where( 'form_id', $form_id )
                ->where( 'meta_key', 'access_lock' )
                ->update( [ 'meta_value' => $lock ] );
        } else {
            $status = FormMeta::query()->insert(
                [
                    'form_id'    => $form_id,
                    'meta_key'   => 'access_lock',
                    'meta_value' => $lock,
                ]
            );
        }

        if ( false === $status ) {
            return null;
        }

        return $lock;
    }

    public function remove_all_access_lock_by_user( int $user_id, string $session_token ) {
        $locks = FormMeta::query()
            ->where( 'meta_key', 'access_lock' )
            ->get();


        foreach ( $locks as $lock ) {
            $lock_data = explode( ':', $lock->meta_value );
            
            if ( count( $lock_data ) < 3 ) {
                continue;
            }

            $lock_user_id = absint( $lock_data[1] );

            if ( $lock_user_id !== $user_id ) {
                continue;
            }

            $lock_session_token = $lock_data[2];

            if ( $lock_session_token !== $session_token ) {
                continue;
            }

            $this->remove_access_lock( $lock->form_id );
        }
    }

    public function remove_access_lock( int $form_id ): bool {
        $status = FormMeta::query()
            ->where( 'form_id', $form_id )
            ->where( 'meta_key', 'access_lock' )
            ->delete();

        return $status === false ? false : true;
    }

    public function get_last_access_lock( int $form_id ): ?array {
        $last_access_lock = FormMeta::query()
            ->where( 'form_id', $form_id )
            ->where( 'meta_key', 'last_access_lock' )
            ->first();

        if ( ! $last_access_lock ) {
            return null;
        }

        $last_agent = explode( ':', $last_access_lock->meta_value );

        if ( count( $last_agent ) < 2 ) {
            return null;
        }

        return [
            'user_id'       => $last_agent[0],
            'session_token' => $last_agent[1],
        ];
    }

    public function set_last_access_lock( int $form_id, int $user_id, string $session_token ): bool {
        $status = FormMeta::query()->insert(
            [
                'form_id'    => $form_id,
                'meta_key'   => 'last_access_lock',
                'meta_value' => "{$user_id}:{$session_token}",
            ]
        );
        
        return $status === false ? false : true;
    }

    public function remove_last_access_lock_by_user( int $user_id, string $session_token ): bool {
        $status = FormMeta::query()
            ->where( 'meta_key', 'last_access_lock' )
            ->where( 'meta_value', "{$user_id}:{$session_token}" )
            ->delete();

        return $status === false ? false : true;
    }

    public function remove_last_access_lock( int $form_id ): bool {
        $status = FormMeta::query()
                    ->where(
                        [
                            'form_id'  => $form_id,
                            'meta_key' => 'last_access_lock',
                        ]
                    )->delete();

        return $status === false ? false : true;
    }

    public function take_over_all_access_lock( $user_id, $session_token ): void {
        $locks = FormMeta::query()
            ->where( 'meta_key', 'access_lock' )
            ->get();

        foreach ( $locks as $lock_data ) {
            $this->take_over_access_lock( $lock_data->form_id, $user_id, $session_token );
        }
    }

    public function take_over_access_lock( int $form_id, $user_id, $session_token ): ?string {
        $current_lock = $this->check_access_lock( $form_id, $user_id, $session_token );

        if ( $current_lock['is_locked'] ) {
            $this->set_last_access_lock( $form_id, $current_lock['locked_by_user_id'], $current_lock['locked_by_session_token'] );
        }
        
        return $this->set_access_lock( $form_id, $user_id, $session_token );
    }
}
