<?php
/**
 * [1.6.118.87] Equipas & Pessoas — banco de pessoas reutilizável.
 *
 * O admin cria membros de equipa UMA vez (banco de pessoas) e depois
 * atribui-os a projectos. As infos aparecem ao artista no detalhe do
 * projecto, mostrando quem vai trabalhar no videoclipe dele.
 *
 * Arquitectura:
 *  - Tabela `rmbz_team_members`: o banco de pessoas (reutilizável).
 *  - A atribuição a um projecto fica em extra_data['team_member_ids'] do
 *    próprio projecto (array de IDs), evitando uma tabela de junção.
 *
 * Visibilidade dos campos:
 *  - Nome, foto, cargo, especialidade, bio, redes → visíveis ao ARTISTA.
 *  - Contacto (telefone/email) → SÓ admin.
 *
 * Local: includes/admin/project-team.php
 */
if ( ! defined( 'ABSPATH' ) ) { exit; }

// =============================================================================
// [1.6.118.90] CATÁLOGO DE DEPARTAMENTOS E CARGOS (produção audiovisual)
// =============================================================================

/**
 * Estrutura real de uma equipa de produção de videoclipes/filmes, organizada
 * por departamentos. Cada departamento tem: label, ícone (emoji), cor, e a
 * lista de cargos típicos. Baseado nas funções padrão da indústria.
 *
 * @return array<string, array>
 */
function rmbz_team_departments() {
    return [
        'direcao' => [
            'label' => 'Direção & Produção',
            'icon'  => '🎬',
            'color' => '#e53935',
            'roles' => [
                'Realizador / Diretor',
                'Assistente de Realização',
                'Produtor',
                'Produtor Executivo',
                'Diretor de Produção',
                'Assistente de Produção',
                'Argumentista / Guionista',
                'Storyboard Artist',
            ],
        ],
        'camera' => [
            'label' => 'Câmara & Imagem',
            'icon'  => '🎥',
            'color' => '#1e88e5',
            'roles' => [
                'Diretor de Fotografia (DOP)',
                'Operador de Câmara',
                'Assistente de Câmara (Focus Puller)',
                'Operador de Steadicam',
                'Piloto de Drone',
                'DIT (Técnico de Imagem Digital)',
            ],
        ],
        'iluminacao' => [
            'label' => 'Iluminação',
            'icon'  => '💡',
            'color' => '#fbc02d',
            'roles' => [
                'Gaffer (Chefe de Iluminação)',
                'Eletricista',
                'Best Boy',
            ],
        ],
        'grip' => [
            'label' => 'Maquinaria (Grip)',
            'icon'  => '🛠️',
            'color' => '#6d4c41',
            'roles' => [
                'Key Grip',
                'Grip',
                'Operador de Dolly',
            ],
        ],
        'arte' => [
            'label' => 'Arte & Cenografia',
            'icon'  => '🎨',
            'color' => '#8e24aa',
            'roles' => [
                'Diretor de Arte',
                'Cenógrafo / Production Designer',
                'Adereçista (Props)',
                'Guarda-Roupa / Styling',
            ],
        ],
        'maquilhagem' => [
            'label' => 'Cabelo & Maquilhagem',
            'icon'  => '💄',
            'color' => '#ec407a',
            'roles' => [
                'Maquilhador(a)',
                'Cabeleireiro(a)',
                'Maquilhador(a) de Efeitos (SFX)',
            ],
        ],
        'som' => [
            'label' => 'Som',
            'icon'  => '🎙️',
            'color' => '#00897b',
            'roles' => [
                'Técnico de Som',
                'Operador de Boom',
                'Sound Designer',
            ],
        ],
        'pos' => [
            'label' => 'Pós-Produção',
            'icon'  => '✂️',
            'color' => '#3949ab',
            'roles' => [
                'Editor de Vídeo',
                'Colorista',
                'Artista de VFX / Efeitos Visuais',
                'Motion Graphics / Animação',
                'Compositor / Música Original',
            ],
        ],
        'talento' => [
            'label' => 'Talento & Elenco',
            'icon'  => '⭐',
            'color' => '#f0b90b',
            'roles' => [
                'Coreógrafo(a)',
                'Bailarino(a)',
                'Ator / Atriz',
                'Figurante',
                'Modelo',
            ],
        ],
        'outro' => [
            'label' => 'Outro',
            'icon'  => '📌',
            'color' => '#5f6368',
            'roles' => [],
        ],
    ];
}

/**
 * Metadados de um departamento (label/icon/color). Fallback para "outro".
 */
function rmbz_team_department_meta( $key ) {
    $all = rmbz_team_departments();
    return $all[ $key ] ?? $all['outro'];
}

// =============================================================================
// SCHEMA
// =============================================================================

/**
 * Cria/actualiza a tabela do banco de pessoas. Corre em admin_init (cedo).
 */
function rmbz_team_install_schema() {
    global $wpdb;
    $stored = get_option( 'rmbz_team_schema_version', '0' );
    // [1.5.408] 1.4: role alargado (múltiplos cargos) + coluna departments
    // (todos os departamentos da pessoa, CSV — a validação de requisitos usa).
    // [1.5.412] 1.5: acesso ao PORTAL DA EQUIPA no site — pin_hash (PIN de
    // login, hash), pin_must_change (1 = tem de definir novo PIN ao entrar),
    // pin_updated_at e last_login_at.
    if ( version_compare( $stored, '1.5', '>=' ) ) return;

    $t = $wpdb->prefix . 'rmbz_team_members';
    $charset = $wpdb->get_charset_collate();

    require_once ABSPATH . 'wp-admin/includes/upgrade.php';

    $sql = "CREATE TABLE $t (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(160) NOT NULL,
        department VARCHAR(60) DEFAULT NULL,
        departments VARCHAR(255) DEFAULT NULL,
        origin VARCHAR(20) NOT NULL DEFAULT '1mz',
        role VARCHAR(255) DEFAULT NULL,
        specialty VARCHAR(160) DEFAULT NULL,
        bio TEXT DEFAULT NULL,
        photo_url VARCHAR(512) DEFAULT NULL,
        socials TEXT DEFAULT NULL,
        contact_phone VARCHAR(40) DEFAULT NULL,
        contact_email VARCHAR(160) DEFAULT NULL,
        pin_hash VARCHAR(255) DEFAULT NULL,
        pin_must_change TINYINT(1) NOT NULL DEFAULT 1,
        pin_updated_at DATETIME DEFAULT NULL,
        last_login_at DATETIME DEFAULT NULL,
        is_active TINYINT(1) NOT NULL DEFAULT 1,
        sort_order INT NOT NULL DEFAULT 0,
        created_at DATETIME NOT NULL,
        updated_at DATETIME DEFAULT NULL,
        KEY idx_active (is_active),
        KEY idx_sort (sort_order)
    ) $charset;";

    dbDelta( $sql );

    // [1.6.118.89] Tabela de avaliações (preparada para o futuro: o artista
    // avalia a equipa após o projecto). Vazia por agora, mas o dashboard já
    // calcula médias a partir dela.
    $tr = $wpdb->prefix . 'rmbz_team_ratings';
    $sql_r = "CREATE TABLE $tr (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        member_id BIGINT UNSIGNED NOT NULL,
        project_id BIGINT UNSIGNED DEFAULT NULL,
        artist_telemovel VARCHAR(40) DEFAULT NULL,
        stars TINYINT UNSIGNED NOT NULL,
        comment TEXT DEFAULT NULL,
        created_at DATETIME NOT NULL,
        KEY idx_member (member_id),
        KEY idx_project (project_id),
        UNIQUE KEY uq_member_project_artist (member_id, project_id, artist_telemovel)
    ) $charset;";
    dbDelta( $sql_r );

    update_option( 'rmbz_team_schema_version', '1.5' );
}
add_action( 'admin_init', 'rmbz_team_install_schema', 7 );

// [1.5.412] O portal da equipa corre no FRONT (login no site) — o schema tem
// de estar atualizado mesmo sem ninguém abrir o wp-admin depois do deploy.
add_action( 'init', 'rmbz_team_install_schema', 7 );

// =============================================================================
// [1.5.412] ACESSO AO PORTAL DA EQUIPA — PIN por membro (login no site)
// =============================================================================

/**
 * Normaliza um telefone para comparação: só dígitos, últimos 9
 * (846..., +258846..., "84 639 26 36" → '846392636').
 */
function rmbz_team_normalize_phone( $tel ) {
    $digits = preg_replace( '/\D+/', '', (string) $tel );
    return strlen( $digits ) > 9 ? substr( $digits, -9 ) : $digits;
}

/**
 * Encontra um membro ATIVO pelo telefone de contacto (comparação pelos
 * últimos 9 dígitos). Devolve a linha ou null.
 */
function rmbz_team_find_by_phone( $tel ) {
    $needle = rmbz_team_normalize_phone( $tel );
    if ( strlen( $needle ) < 9 ) return null;
    foreach ( (array) rmbz_team_get_all( true ) as $m ) {
        if ( empty( $m->contact_phone ) ) continue;
        if ( rmbz_team_normalize_phone( $m->contact_phone ) === $needle ) return $m;
    }
    return null;
}

/**
 * Define o PIN de acesso de um membro (hash). $must_change=true quando é o
 * admin a definir o PIN inicial — o membro é obrigado a trocar ao entrar.
 * @return true|string true ou mensagem de erro.
 */
function rmbz_team_set_pin( $member_id, $pin, $must_change = true ) {
    global $wpdb;
    $pin = trim( (string) $pin );
    if ( ! preg_match( '/^\d{4,8}$/', $pin ) ) {
        return 'O PIN deve ter 4 a 8 dígitos.';
    }
    $wpdb->update( $wpdb->prefix . 'rmbz_team_members', [
        'pin_hash'        => wp_hash_password( $pin ),
        'pin_must_change' => $must_change ? 1 : 0,
        'pin_updated_at'  => current_time( 'mysql' ),
    ], [ 'id' => (int) $member_id ] );
    return true;
}

/**
 * Verifica o PIN de um membro. Sem pin_hash definido nunca autentica.
 */
function rmbz_team_verify_pin( $member, $pin ) {
    if ( ! $member || empty( $member->pin_hash ) ) return false;
    $pin = trim( (string) $pin );
    if ( $pin === '' ) return false;
    return wp_check_password( $pin, (string) $member->pin_hash );
}

/**
 * Projetos onde um membro está atribuído (extra_data['team_member_ids']).
 * Devolve as linhas completas (com user_nome do artista), mais recentes primeiro.
 */
function rmbz_team_member_projects( $member_id ) {
    global $wpdb;
    $member_id = (int) $member_id;
    if ( $member_id <= 0 ) return [];
    // Pré-filtro barato no SQL (o JSON contém o id) + confirmação em PHP.
    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT p.*, w.user_nome FROM {$wpdb->prefix}rmbz_artist_projects p
         LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON w.user_telemovel = p.artist_telemovel
         WHERE p.extra_data LIKE %s
         ORDER BY p.id DESC",
        '%team_member_ids%'
    ) );
    $out = [];
    foreach ( (array) $rows as $p ) {
        $extra = ! empty( $p->extra_data ) ? json_decode( $p->extra_data, true ) : [];
        $ids = ( is_array( $extra ) && isset( $extra['team_member_ids'] ) && is_array( $extra['team_member_ids'] ) )
            ? array_map( 'intval', $extra['team_member_ids'] ) : [];
        if ( in_array( $member_id, $ids, true ) ) {
            $p->rmbz_extra = is_array( $extra ) ? $extra : [];
            $out[] = $p;
        }
    }
    return $out;
}

// =============================================================================
// CRUD — banco de pessoas
// =============================================================================

/**
 * Devolve todos os membros activos, ordenados.
 * @param bool $only_active Se true (default), só os activos.
 * @return array Lista de objectos.
 */
function rmbz_team_get_all( $only_active = true ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_team_members';
    $where = $only_active ? 'WHERE is_active = 1' : '';
    return $wpdb->get_results( "SELECT * FROM $t $where ORDER BY sort_order ASC, name ASC" );
}

/**
 * Devolve um membro por ID.
 */
function rmbz_team_get( $id ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_team_members';
    return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $t WHERE id = %d", (int) $id ) );
}

/**
 * Devolve vários membros pelos IDs, na ordem dada.
 * @param array $ids Lista de IDs.
 * @return array Objectos, na ordem dos IDs (ignora IDs inexistentes/inactivos).
 */
function rmbz_team_get_many( $ids ) {
    if ( empty( $ids ) || ! is_array( $ids ) ) return [];
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_team_members';
    $ids = array_values( array_unique( array_map( 'intval', $ids ) ) );
    $ids = array_filter( $ids, fn( $v ) => $v > 0 );
    if ( empty( $ids ) ) return [];

    $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT * FROM $t WHERE id IN ($placeholders) AND is_active = 1",
        ...$ids
    ) );

    // Reordenar conforme a ordem pedida em $ids
    $by_id = [];
    foreach ( $rows as $r ) { $by_id[ (int) $r->id ] = $r; }
    $ordered = [];
    foreach ( $ids as $id ) {
        if ( isset( $by_id[ $id ] ) ) $ordered[] = $by_id[ $id ];
    }
    return $ordered;
}

/**
 * Cria ou actualiza um membro.
 * @param array $data Campos do membro.
 * @param int   $id   Se >0, actualiza; senão cria.
 * @return int|false ID do membro, ou false em erro.
 */
function rmbz_team_save( $data, $id = 0 ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_team_members';

    $name = isset( $data['name'] ) ? sanitize_text_field( $data['name'] ) : '';
    if ( $name === '' ) return false; // nome é obrigatório

    // socials: aceita array (rede=>url) e guarda como JSON
    $socials_json = null;
    if ( ! empty( $data['socials'] ) ) {
        $socials = is_array( $data['socials'] ) ? $data['socials'] : [];
        // limpar entradas vazias + sanitizar URLs
        $clean = [];
        foreach ( $socials as $k => $v ) {
            $v = trim( (string) $v );
            if ( $v !== '' ) $clean[ sanitize_key( $k ) ] = esc_url_raw( $v );
        }
        if ( ! empty( $clean ) ) $socials_json = wp_json_encode( $clean );
    }

    // [1.5.408] MÚLTIPLOS CARGOS: o form novo envia 'roles' como array de
    // strings "deptkey|Cargo" (chips do catálogo) + 'roles_free' (texto livre,
    // separado por vírgulas). Compomos:
    //   role        = todos os cargos juntos ("Editor de Vídeo · Realizador")
    //   department  = 1.º departamento (compat: agrupamento, taxa)
    //   departments = CSV de TODOS os departamentos (validação de requisitos)
    $role_str  = isset( $data['role'] ) ? sanitize_text_field( $data['role'] ) : null;
    $dept_str  = isset( $data['department'] ) ? sanitize_text_field( $data['department'] ) : null;
    $depts_csv = null;
    if ( ( ! empty( $data['roles'] ) && is_array( $data['roles'] ) ) || ! empty( $data['roles_free'] ) ) {
        $labels = [];
        $dkeys  = [];
        foreach ( (array) ( $data['roles'] ?? [] ) as $pair ) {
            $pair = (string) $pair;
            $pos  = strpos( $pair, '|' );
            if ( $pos === false ) continue;
            $dk = sanitize_key( substr( $pair, 0, $pos ) );
            $lb = sanitize_text_field( substr( $pair, $pos + 1 ) );
            if ( $lb === '' ) continue;
            $labels[] = $lb;
            if ( $dk !== '' ) $dkeys[] = $dk;
        }
        // Cargos livres ("outros"), separados por vírgula.
        if ( ! empty( $data['roles_free'] ) ) {
            foreach ( explode( ',', (string) $data['roles_free'] ) as $free ) {
                $free = sanitize_text_field( trim( $free ) );
                if ( $free !== '' ) $labels[] = $free;
            }
        }
        $labels = array_values( array_unique( $labels ) );
        $dkeys  = array_values( array_unique( $dkeys ) );
        if ( ! empty( $labels ) ) {
            $role_str  = mb_substr( implode( ' · ', $labels ), 0, 255 );
            $dept_str  = $dkeys[0] ?? $dept_str;
            $depts_csv = ! empty( $dkeys ) ? implode( ',', $dkeys ) : null;
        }
    }

    $fields = [
        'name'          => $name,
        'department'    => $dept_str,
        'departments'   => $depts_csv,
        'origin'        => ( isset( $data['origin'] ) && $data['origin'] === 'externo' ) ? 'externo' : '1mz',
        'role'          => $role_str,
        'specialty'     => isset( $data['specialty'] ) ? sanitize_text_field( $data['specialty'] ) : null,
        'bio'           => isset( $data['bio'] ) ? sanitize_textarea_field( $data['bio'] ) : null,
        'photo_url'     => isset( $data['photo_url'] ) ? esc_url_raw( $data['photo_url'] ) : null,
        'socials'       => $socials_json,
        'contact_phone' => isset( $data['contact_phone'] ) ? sanitize_text_field( $data['contact_phone'] ) : null,
        'contact_email' => isset( $data['contact_email'] ) ? sanitize_email( $data['contact_email'] ) : null,
        'is_active'     => isset( $data['is_active'] ) ? ( (int) $data['is_active'] ? 1 : 0 ) : 1,
        'sort_order'    => isset( $data['sort_order'] ) ? (int) $data['sort_order'] : 0,
    ];

    if ( $id > 0 ) {
        $fields['updated_at'] = current_time( 'mysql' );
        $ok = $wpdb->update( $t, $fields, [ 'id' => (int) $id ] );
        return $ok !== false ? (int) $id : false;
    } else {
        $fields['created_at'] = current_time( 'mysql' );
        $ok = $wpdb->insert( $t, $fields );
        return $ok ? (int) $wpdb->insert_id : false;
    }
}

/**
 * Apaga (soft delete) um membro — marca inactivo para não perder histórico
 * de projectos onde já foi atribuído.
 */
function rmbz_team_delete( $id ) {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_team_members';
    return $wpdb->update(
        $t,
        [ 'is_active' => 0, 'updated_at' => current_time( 'mysql' ) ],
        [ 'id' => (int) $id ]
    );
}

// =============================================================================
// FORMATAÇÃO — para a API / artista (esconde contacto)
// =============================================================================

/**
 * Formata um membro para enviar ao ARTISTA (app/site).
 * NÃO inclui contacto (phone/email) — esses são só para o admin.
 *
 * @param object $m Linha da BD.
 * @return array
 */
function rmbz_team_format_public( $m ) {
    $socials = [];
    $roles = array_values( array_filter( array_map(
        'trim',
        preg_split( '/\s*\x{00B7}\s*/u', (string) ( $m->role ?? '' ) ) ?: []
    ) ) );
    if ( ! empty( $m->socials ) ) {
        $decoded = json_decode( $m->socials, true );
        if ( is_array( $decoded ) ) $socials = $decoded;
    }
    return [
        'id'        => (int) $m->id,
        'name'      => (string) $m->name,
        'department'=> $m->department ? (string) $m->department : '',
        // [1.6.118.92] Label + ícone do departamento para o app/site mostrarem
        // sem precisar do catálogo. Vazios se sem departamento.
        'department_label' => $m->department ? (string) rmbz_team_department_meta( $m->department )['label'] : '',
        'department_icon'  => $m->department ? (string) rmbz_team_department_meta( $m->department )['icon'] : '',
        'origin'    => isset( $m->origin ) ? (string) $m->origin : '1mz',
        'role'      => $m->role ? (string) $m->role : '',
        'roles'     => $roles,
        'specialty' => $m->specialty ? (string) $m->specialty : '',
        'bio'       => $m->bio ? (string) $m->bio : '',
        'photo_url' => $m->photo_url ? (string) $m->photo_url : '',
        'socials'   => (object) $socials,
        // contacto OMITIDO de propósito (só admin)
    ];
}

/**
 * Devolve a equipa atribuída a um projecto, formatada para o artista.
 *
 * Mostra exclusivamente os cargos escolhidos pelo admin NESTE projeto
 * (extra_data['team_project_roles'][member_id] = [cargo, ...]). O cargo geral
 * do perfil nunca e usado como fallback no painel do artista.
 *
 * @param array $extra extra_data do projecto (já descodificado).
 * @return array Lista de membros públicos (pode ser vazia).
 */
function rmbz_team_for_project( $extra ) {
    if ( ! is_array( $extra ) ) return [];
    $ids = $extra['team_member_ids'] ?? [];
    if ( empty( $ids ) || ! is_array( $ids ) ) return [];
    $proj_roles = ( isset( $extra['team_project_roles'] ) && is_array( $extra['team_project_roles'] ) )
        ? $extra['team_project_roles'] : [];
    $members = rmbz_team_get_many( $ids );
    $out = [];
    foreach ( $members as $m ) {
        $pub = rmbz_team_format_public( $m );
        $sel = $proj_roles[ (int) $m->id ] ?? null;
        $pub['role']  = '';
        $pub['roles'] = [];
        if ( is_array( $sel ) ) {
            $sel = array_values( array_filter( array_map( 'sanitize_text_field', $sel ) ) );
            if ( ! empty( $sel ) ) {
                $pub['role']  = implode( ' · ', $sel );
                $pub['roles'] = $sel;
            }
        }
        $out[] = $pub;
    }
    return $out;
}

// =============================================================================
// [1.6.118.89] ESTATÍSTICAS — nº de trabalhos + média de avaliação
// =============================================================================

/**
 * Conta em quantos projectos um membro está atribuído.
 * Procura o ID dentro de extra_data['team_member_ids'] (JSON).
 *
 * @param int $member_id
 * @return int
 */
function rmbz_team_count_projects( $member_id ) {
    global $wpdb;
    $member_id = (int) $member_id;
    if ( $member_id <= 0 ) return 0;

    $table = $wpdb->prefix . 'rmbz_artist_projects';
    // LIKE grosseiro para reduzir candidatos; confirmamos em PHP para evitar
    // falsos positivos (ex: id 1 vs 12). O padrão procura o número entre
    // aspas/vírgulas/parêntesis dentro do array JSON.
    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT extra_data FROM $table WHERE extra_data LIKE %s",
        '%team_member_ids%' . $member_id . '%'
    ) );

    $count = 0;
    foreach ( $rows as $r ) {
        $extra = ! empty( $r->extra_data ) ? json_decode( $r->extra_data, true ) : [];
        if ( ! is_array( $extra ) ) continue;
        $ids = $extra['team_member_ids'] ?? [];
        if ( is_array( $ids ) && in_array( $member_id, array_map( 'intval', $ids ), true ) ) {
            $count++;
        }
    }
    return $count;
}

/**
 * Média de estrelas + total de avaliações de um membro.
 * @param int $member_id
 * @return array ['avg' => float, 'count' => int]
 */
function rmbz_team_rating_stats( $member_id ) {
    global $wpdb;
    $member_id = (int) $member_id;
    if ( $member_id <= 0 ) return [ 'avg' => 0.0, 'count' => 0 ];

    $tr = $wpdb->prefix . 'rmbz_team_ratings';
    // A tabela pode não existir ainda em instalações muito antigas — proteger.
    if ( $wpdb->get_var( "SHOW TABLES LIKE '$tr'" ) !== $tr ) {
        return [ 'avg' => 0.0, 'count' => 0 ];
    }
    $row = $wpdb->get_row( $wpdb->prepare(
        "SELECT AVG(stars) AS avg_stars, COUNT(*) AS n FROM $tr WHERE member_id = %d",
        $member_id
    ) );
    return [
        'avg'   => $row && $row->avg_stars !== null ? round( (float) $row->avg_stars, 1 ) : 0.0,
        'count' => $row ? (int) $row->n : 0,
    ];
}

/**
 * Devolve um membro com estatísticas agregadas (para o dashboard).
 * @param object $m Linha da BD.
 * @return array
 */
function rmbz_team_with_stats( $m ) {
    $rating = rmbz_team_rating_stats( $m->id );
    return [
        'member'        => $m,
        'projects'      => rmbz_team_count_projects( $m->id ),
        'rating_avg'    => $rating['avg'],
        'rating_count'  => $rating['count'],
    ];
}

// =============================================================================
// [1.6.118.98] MOTOR DE REQUISITOS — cargos obrigatórios + validação
// =============================================================================

/**
 * Departamentos considerados ESSENCIAIS por defeito para um videoclipe poder
 * avançar. A 1MZ pode personalizar via opção 'rmbz_team_required_depts'.
 *
 * Guardamos por DEPARTAMENTO (não cargo exacto) — é mais robusto: basta haver
 * alguém da Direção, alguém da Câmara e alguém da Pós para o essencial existir.
 *
 * @return array Lista de chaves de departamento obrigatórias.
 */
function rmbz_team_required_departments() {
    $default = [ 'direcao', 'camera', 'pos' ]; // Direção, Câmara, Pós-Produção
    $saved = get_option( 'rmbz_team_required_depts', null );
    if ( is_array( $saved ) ) return $saved;
    return $default;
}

/**
 * Valida a equipa atribuída a um projecto e devolve um diagnóstico completo.
 *
 * @param array $extra extra_data do projecto (já descodificado).
 * @return array {
 *   'ok'              => bool,   // true se todos os requisitos cumpridos
 *   'missing'         => array,  // departamentos obrigatórios em falta [{key,label,icon}]
 *   'external_by_mz'  => array,  // membros externos atribuídos pela 1MZ
 *   'external_by_artist' => array, // membros externos pedidos pelo artista
 *   'has_external'    => bool,
 *   'member_count'    => int,
 * }
 */
function rmbz_team_validate_project( $extra ) {
    $result = [
        'ok'                 => true,
        'missing'            => [],
        'external_by_mz'     => [],
        'external_by_artist' => [],
        'has_external'       => false,
        'member_count'       => 0,
    ];

    $ids = is_array( $extra ) ? ( $extra['team_member_ids'] ?? [] ) : [];
    // Quem pediu cada membro: extra_data['team_requested_by'] = [member_id => 'artist'|'mz']
    $requested_by = is_array( $extra ) ? ( $extra['team_requested_by'] ?? [] ) : [];

    $members = ( ! empty( $ids ) && is_array( $ids ) ) ? rmbz_team_get_many( $ids ) : [];
    $result['member_count'] = count( $members );

    // 1) Departamentos presentes
    // [1.5.408] Uma pessoa pode exercer VÁRIOS cargos em departamentos
    // diferentes (ex.: Editor + Realizador) — a coluna `departments` (CSV)
    // conta TODOS; fallback para `department` (registos antigos).
    $present_depts = [];
    foreach ( $members as $m ) {
        $m_depts = [];
        if ( ! empty( $m->departments ) ) {
            $m_depts = array_filter( array_map( 'trim', explode( ',', (string) $m->departments ) ) );
        } elseif ( ! empty( $m->department ) ) {
            $m_depts = [ (string) $m->department ];
        }
        foreach ( $m_depts as $mdk ) $present_depts[ $mdk ] = true;

        // 2) Membros externos + quem pediu
        if ( isset( $m->origin ) && $m->origin === 'externo' ) {
            $result['has_external'] = true;
            $who = $requested_by[ (int) $m->id ] ?? 'mz';
            if ( $who === 'artist' ) {
                $result['external_by_artist'][] = $m;
            } else {
                $result['external_by_mz'][] = $m;
            }
        }
    }

    // 3) Departamentos obrigatórios em falta
    $depts_meta = rmbz_team_departments();
    foreach ( rmbz_team_required_departments() as $req ) {
        if ( empty( $present_depts[ $req ] ) ) {
            $meta = $depts_meta[ $req ] ?? [ 'label' => $req, 'icon' => '📌' ];
            $result['missing'][] = [
                'key'   => $req,
                'label' => $meta['label'],
                'icon'  => $meta['icon'],
            ];
        }
    }

    $result['ok'] = empty( $result['missing'] );
    return $result;
}

/**
 * Taxa de vistoria aplicada quando o artista pede uma pessoa externa que a
 * 1MZ não conhece (garantia de qualidade 1MusicMoz). Configurável.
 *
 * [1.6.118.102] Agora aceita um departamento opcional: se houver taxa
 * específica para esse departamento, usa-a; senão cai na taxa base.
 *
 * @param string $department Chave do departamento (ex: 'pos', 'camera'). Opcional.
 * @return float
 */
function rmbz_team_inspection_fee( $department = '' ) {
    $base = get_option( 'rmbz_team_inspection_fee', 1500 );
    $base = is_numeric( $base ) ? (float) $base : 1500.0;

    if ( $department !== '' ) {
        $per_dept = get_option( 'rmbz_team_inspection_fee_by_dept', [] );
        if ( is_array( $per_dept ) && isset( $per_dept[ $department ] ) && is_numeric( $per_dept[ $department ] ) ) {
            return (float) $per_dept[ $department ];
        }
    }
    return $base;
}

/**
 * [1.6.118.98] Guarda a configuração de requisitos (departamentos obrigatórios
 * + taxa de vistoria).
 */
function rmbz_team_handle_save_config() {
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );
    check_admin_referer( 'rmbz_team_save_config_nonce' );

    $depts = rmbz_team_departments();
    $req = [];
    if ( isset( $_POST['required_depts'] ) && is_array( $_POST['required_depts'] ) ) {
        foreach ( $_POST['required_depts'] as $d ) {
            $d = sanitize_text_field( $d );
            if ( isset( $depts[ $d ] ) ) $req[] = $d;
        }
    }
    update_option( 'rmbz_team_required_depts', $req );

    $fee = isset( $_POST['inspection_fee'] ) ? (float) $_POST['inspection_fee'] : 1500;
    update_option( 'rmbz_team_inspection_fee', max( 0, $fee ) );

    // [1.6.118.102] Taxas por departamento. Vazio = usa a taxa base.
    $by_dept = [];
    if ( isset( $_POST['fee_by_dept'] ) && is_array( $_POST['fee_by_dept'] ) ) {
        foreach ( $_POST['fee_by_dept'] as $dkey => $val ) {
            $dkey = sanitize_text_field( $dkey );
            // Só guarda se: departamento válido E valor preenchido e numérico
            if ( isset( $depts[ $dkey ] ) && $val !== '' && is_numeric( $val ) ) {
                $by_dept[ $dkey ] = max( 0, (float) $val );
            }
        }
    }
    update_option( 'rmbz_team_inspection_fee_by_dept', $by_dept );

    wp_safe_redirect( admin_url( 'admin.php?page=rmbz-projetos&action=team&config_saved=1' ) );
    exit;
}
add_action( 'admin_post_rmbz_team_save_config', 'rmbz_team_handle_save_config' );

/**
 * [1.5.408] Linhas de taxa de vistoria de UMA pessoa num projeto — uma por
 * DEPARTAMENTO coberto pelos cargos que ela vai exercer NESTE projeto
 * (team_project_roles; sem escolha = todos os cargos dela). Uma pessoa que é
 * Realizador (Direção) + Editor (Pós) paga DUAS vistorias, cada uma com a
 * taxa do seu departamento.
 *
 * @param object $m     Membro (linha da BD).
 * @param array  $extra extra_data do projecto (descodificado).
 * @return array [ dept_key => [ 'label', 'icon', 'fee', 'roles' => [..] ] ]
 */
function rmbz_team_member_fee_lines( $m, $extra ) {
    $catalog = rmbz_team_departments();
    $mid = (int) $m->id;

    // Cargos deste projeto (escolhidos) ou todos os da pessoa.
    $proj_roles = ( is_array( $extra ) && isset( $extra['team_project_roles'][ $mid ] ) && is_array( $extra['team_project_roles'][ $mid ] ) )
        ? $extra['team_project_roles'][ $mid ] : null;
    $all_roles = array_values( array_filter( array_map( 'trim', explode( '·', (string) ( $m->role ?? '' ) ) ) ) );
    $roles = ( is_array( $proj_roles ) && ! empty( $proj_roles ) ) ? $proj_roles : $all_roles;

    // Mapear cargo → departamento pelo catálogo (livres caem no dept principal).
    $role_dept = [];
    foreach ( $catalog as $dk => $d ) {
        foreach ( $d['roles'] as $r ) $role_dept[ $r ] = $dk;
    }

    $lines = [];
    foreach ( $roles as $r ) {
        $dk = $role_dept[ $r ] ?? (string) ( $m->department ?: 'outro' );
        if ( ! isset( $lines[ $dk ] ) ) {
            $meta = rmbz_team_department_meta( $dk );
            $lines[ $dk ] = [
                'label' => (string) $meta['label'],
                'icon'  => (string) $meta['icon'],
                'fee'   => (float) rmbz_team_inspection_fee( $dk ),
                'roles' => [],
            ];
        }
        $lines[ $dk ]['roles'][] = $r;
    }
    // Sem cargos definidos: uma linha do departamento principal (compat).
    if ( empty( $lines ) ) {
        $dk = (string) ( $m->department ?: 'outro' );
        $meta = rmbz_team_department_meta( $dk );
        $lines[ $dk ] = [
            'label' => (string) $meta['label'],
            'icon'  => (string) $meta['icon'],
            'fee'   => (float) rmbz_team_inspection_fee( $dk ),
            'roles' => [],
        ];
    }
    return $lines;
}

/**
 * [1.5.409] ESTADO DA TAXA DE VISTORIA de um projeto — a taxa é devida
 * QUANDO A FILMAGEM ACABA: ao passar para EDIÇÃO, se ainda não estiver
 * coberta pelos pagamentos, o projeto fica BLOQUEADO (a equipa não edita
 * nem entrega ficheiros e o artista vê o motivo no painel).
 *
 * Os pagamentos do projeto cobrem a vistoria EM ÚLTIMO lugar: primeiro o
 * resto do orçamento obrigatório, depois a vistoria. Assim o artista paga
 * pelo fluxo normal de pagamento — sem endpoint novo.
 *
 * @param object|int $project Linha (id, production_status, scenario_details,
 *                            amount_paid) ou ID do projeto.
 * @return array [ 'total'=>float, 'left'=>float, 'blocked'=>bool ]
 */
function rmbz_team_vistoria_status( $project ) {
    global $wpdb;
    if ( is_numeric( $project ) ) {
        $project = $wpdb->get_row( $wpdb->prepare(
            "SELECT id, production_status, scenario_details, amount_paid FROM {$wpdb->prefix}rmbz_artist_projects WHERE id=%d",
            (int) $project
        ) );
    }
    $out = [ 'total' => 0.0, 'left' => 0.0, 'blocked' => false, 'editor' => false ];
    if ( ! $project ) return $out;

    $details = ! empty( $project->scenario_details ) ? json_decode( $project->scenario_details, true ) : [];
    if ( ! is_array( $details ) ) return $out;

    $vistoria_total = 0.0;
    $other_oblig    = 0.0;
    $has_editor     = false; // [1.5.410] vistoria de EDITOR externo (Pós-Produção)?
    foreach ( $details as $it ) {
        if ( ! is_array( $it ) ) continue;
        $cost = isset( $it['cost'] ) ? (float) $it['cost'] : 0.0;
        if ( ! empty( $it['team_fee_for'] ) ) {
            $vistoria_total += $cost;
            if ( (string) ( $it['team_fee_dept'] ?? '' ) === 'pos' ) $has_editor = true;
        } elseif ( ! isset( $it['must_pay'] ) || (int) $it['must_pay'] === 1 ) {
            // Itens obrigatórios (legado sem must_pay = obrigatório),
            // exceto os assumidos pelo artista fora da plataforma.
            if ( empty( $it['artist_assumes'] ) ) $other_oblig += $cost;
        }
    }
    $out['editor'] = $has_editor;
    if ( $vistoria_total <= 0 ) return $out;

    $paid = (float) ( $project->amount_paid ?? 0 );
    $vistoria_paid = max( 0.0, min( $vistoria_total, $paid - $other_oblig ) );
    $left = round( $vistoria_total - $vistoria_paid, 2 );

    $out['total'] = round( $vistoria_total, 2 );
    $out['left']  = max( 0.0, $left );
    // Bloqueia da EDIÇÃO em diante (a filmagem acabou) enquanto houver dívida.
    $out['blocked'] = $out['left'] > 0
        && in_array( (string) ( $project->production_status ?? '' ), [ 'editing', 'done' ], true );
    return $out;
}

/**
 * [1.5.409] Notifica o artista (todos os canais) quando o projeto entra em
 * EDIÇÃO com a vistoria por pagar — explica o bloqueio e como resolver.
 * Idempotente por transição (marca extra['vistoria_block_notified_at']).
 */
function rmbz_team_vistoria_notify_if_blocked( $project_id ) {
    global $wpdb;
    $p = $wpdb->get_row( $wpdb->prepare(
        "SELECT p.*, w.user_nome FROM {$wpdb->prefix}rmbz_artist_projects p
         LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON w.user_telemovel = p.artist_telemovel
         WHERE p.id=%d", (int) $project_id
    ) );
    if ( ! $p ) return false;
    $vs = rmbz_team_vistoria_status( $p );
    if ( empty( $vs['blocked'] ) ) return false;

    $valor = number_format( $vs['left'], 2, ',', '.' ) . ' MT';
    // [1.5.410] Vídeo vai ser editado por um EDITOR EXTERNO com taxa por
    // pagar → aviso específico: os ficheiros não são entregues ao editor.
    $is_editor = ! empty( $vs['editor'] );
    if ( function_exists( 'rmbz_notify_user' ) ) {
        rmbz_notify_user(
            (string) $p->artist_telemovel,
            $is_editor ? '🔒 Ficheiros retidos — taxa do editor por pagar' : '🔒 Projeto bloqueado — taxa de vistoria',
            $is_editor
                ? sprintf( 'A filmagem de "%s" terminou a 100%%, mas o teu vídeo vai ser editado por um EDITOR EXTERNO e a taxa de vistoria (%s) ainda não foi paga. Os ficheiros NÃO vão ser entregues ao editor sem regularizares a taxa — entra no projeto e paga, para a equipa preparar os ficheiros e entregar ao editor.', (string) $p->project_title, $valor )
                : sprintf( 'A filmagem de "%s" terminou, mas a taxa de vistoria (%s) ainda não foi paga. A equipa NÃO pode editar nem entregar ficheiros até ser paga — faz o pagamento no projeto para desbloquear.', (string) $p->project_title, $valor ),
            'error'
        );
    }
    if ( function_exists( 'rmbz_notify' ) ) {
        rmbz_notify( (string) $p->artist_telemovel, 'vistoria_due', [
            'nome_artista' => (string) ( $p->user_nome ?: 'Artista' ),
            'musica'       => (string) $p->project_title,
            'valor'        => $valor,
            'editor_nota'  => $is_editor
                ? "\n⚠️ O teu vídeo vai ser editado por um EDITOR EXTERNO: os ficheiros NÃO serão entregues ao editor sem a taxa regularizada. Entra no projeto e paga para a equipa preparar os ficheiros e entregar ao editor.\n"
                : '',
            'site_name'    => get_bloginfo( 'name' ),
        ] );
    }
    if ( function_exists( 'rmbz_pchat_append_change_log' ) ) {
        rmbz_pchat_append_change_log( (int) $project_id, [
            'who'  => 'sistema',
            'what' => '🔒 Projeto BLOQUEADO em edição — taxa de vistoria por pagar (' . $valor . ')',
        ] );
    }
    return true;
}

/**
 * [1.5.411] ENTREGA AO EDITOR EXTERNO — o passo a seguir ao pagamento da
 * taxa do editor. Quando a vistoria de Pós-Produção fica paga, o admin:
 *   1) regista os ficheiros preparados (quantos são, GB, qualidade) →
 *      o artista é notificado com os dados;
 *   2) só depois pode marcar "Entregue ao editor" → o artista é avisado
 *      que o editor já levou todos os ficheiros e a 1MZ só aguarda a
 *      edição para aprovar o vídeo final.
 * Guardado em extra_data['editor_delivery'].
 */
function rmbz_editor_delivery_get( $extra ) {
    $d = ( isset( $extra['editor_delivery'] ) && is_array( $extra['editor_delivery'] ) )
        ? $extra['editor_delivery'] : [];
    return [
        'files'        => isset( $d['files'] ) ? (int) $d['files'] : 0,
        'size_gb'      => isset( $d['size_gb'] ) ? (float) $d['size_gb'] : 0.0,
        'quality'      => (string) ( $d['quality'] ?? '' ),
        'info_at'      => (string) ( $d['info_at'] ?? '' ),
        'info_by'      => (string) ( $d['info_by'] ?? '' ),
        'delivered'    => ! empty( $d['delivered'] ),
        'delivered_at' => (string) ( $d['delivered_at'] ?? '' ),
        'delivered_by' => (string) ( $d['delivered_by'] ?? '' ),
    ];
}

/**
 * O passo de entrega só existe quando: há editor externo (vistoria 'pos'),
 * a taxa está PAGA (left = 0) e a filmagem já acabou (edição em diante).
 */
function rmbz_editor_delivery_available( $project ) {
    $st = (string) ( $project->production_status ?? '' );
    if ( ! in_array( $st, [ 'editing', 'done' ], true ) ) return false;
    $vs = rmbz_team_vistoria_status( $project );
    return ! empty( $vs['editor'] ) && $vs['total'] > 0 && $vs['left'] <= 0;
}

/**
 * Passo 1 — admin regista os ficheiros preparados para o editor.
 * @return true|string true em sucesso, mensagem de erro em falha.
 */
function rmbz_editor_delivery_set_info( $project_id, $files, $size_gb, $quality, $by = 'admin' ) {
    global $wpdb;
    $table = $wpdb->prefix . 'rmbz_artist_projects';
    $p = $wpdb->get_row( $wpdb->prepare(
        "SELECT p.*, w.user_nome FROM {$table} p
         LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON w.user_telemovel = p.artist_telemovel
         WHERE p.id=%d", (int) $project_id
    ) );
    if ( ! $p ) return 'Projeto não encontrado.';
    if ( ! rmbz_editor_delivery_available( $p ) ) {
        return 'Este passo só fica disponível com editor externo, taxa paga e filmagem terminada.';
    }
    $files   = max( 0, (int) $files );
    $size_gb = max( 0.0, round( (float) $size_gb, 2 ) );
    $quality = sanitize_text_field( (string) $quality );
    if ( $files <= 0 || $quality === '' ) {
        return 'Indica quantos ficheiros são e a qualidade.';
    }

    $extra = ! empty( $p->extra_data ) ? json_decode( $p->extra_data, true ) : [];
    if ( ! is_array( $extra ) ) $extra = [];
    $d = rmbz_editor_delivery_get( $extra );
    if ( $d['delivered'] ) return 'Os ficheiros já foram entregues ao editor — não é possível alterar.';

    $extra['editor_delivery'] = [
        'files'        => $files,
        'size_gb'      => $size_gb,
        'quality'      => $quality,
        'info_at'      => current_time( 'mysql' ),
        'info_by'      => (string) $by,
        'delivered'    => false,
        'delivered_at' => '',
        'delivered_by' => '',
    ];
    $wpdb->update( $table, [ 'extra_data' => wp_json_encode( $extra ) ], [ 'id' => (int) $project_id ] );

    $gb_txt = $size_gb > 0 ? number_format( $size_gb, 2, ',', '.' ) . ' GB' : '—';
    if ( function_exists( 'rmbz_notify_user' ) ) {
        rmbz_notify_user(
            (string) $p->artist_telemovel,
            '📦 Ficheiros prontos para o editor',
            sprintf(
                'A equipa preparou os ficheiros do teu videoclipe "%s" para o EDITOR EXTERNO: %d ficheiro(s) · %s · qualidade %s. Falta só a entrega ao editor — avisamos-te assim que ele os levar.',
                (string) $p->project_title, $files, $gb_txt, $quality
            ),
            'success'
        );
    }
    if ( function_exists( 'rmbz_notify' ) ) {
        rmbz_notify( (string) $p->artist_telemovel, 'editor_files_ready', [
            'nome_artista' => (string) ( $p->user_nome ?: 'Artista' ),
            'musica'       => (string) $p->project_title,
            'ficheiros'    => (string) $files,
            'tamanho'      => $gb_txt,
            'qualidade'    => $quality,
            'site_name'    => get_bloginfo( 'name' ),
        ] );
    }
    if ( function_exists( 'rmbz_pchat_append_change_log' ) ) {
        rmbz_pchat_append_change_log( (int) $project_id, [
            'who'  => (string) $by,
            'what' => sprintf( '📦 Ficheiros para o editor externo registados: %d ficheiro(s) · %s · %s', $files, $gb_txt, $quality ),
        ] );
    }
    return true;
}

/**
 * Passo 2 — admin marca a entrega ao editor (só depois do passo 1).
 * @return true|string
 */
function rmbz_editor_delivery_mark_delivered( $project_id, $by = 'admin' ) {
    global $wpdb;
    $table = $wpdb->prefix . 'rmbz_artist_projects';
    $p = $wpdb->get_row( $wpdb->prepare(
        "SELECT p.*, w.user_nome FROM {$table} p
         LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON w.user_telemovel = p.artist_telemovel
         WHERE p.id=%d", (int) $project_id
    ) );
    if ( ! $p ) return 'Projeto não encontrado.';

    $extra = ! empty( $p->extra_data ) ? json_decode( $p->extra_data, true ) : [];
    if ( ! is_array( $extra ) ) $extra = [];
    $d = rmbz_editor_delivery_get( $extra );
    if ( $d['files'] <= 0 )   return 'Primeiro regista os ficheiros (quantos, GB e qualidade).';
    if ( $d['delivered'] )    return 'A entrega ao editor já estava marcada.';

    $extra['editor_delivery']['delivered']    = true;
    $extra['editor_delivery']['delivered_at'] = current_time( 'mysql' );
    $extra['editor_delivery']['delivered_by'] = (string) $by;
    $wpdb->update( $table, [ 'extra_data' => wp_json_encode( $extra ) ], [ 'id' => (int) $project_id ] );

    $gb_txt = $d['size_gb'] > 0 ? number_format( $d['size_gb'], 2, ',', '.' ) . ' GB' : '—';
    if ( function_exists( 'rmbz_notify_user' ) ) {
        rmbz_notify_user(
            (string) $p->artist_telemovel,
            '🚚 Editor já levou todos os ficheiros',
            sprintf(
                'O EDITOR EXTERNO já levou TODOS os ficheiros do teu videoclipe "%s" (%d ficheiro(s) · %s · %s). A 1MZ agora só aguarda a edição para aprovar o vídeo final.',
                (string) $p->project_title, $d['files'], $gb_txt, $d['quality']
            ),
            'success'
        );
    }
    if ( function_exists( 'rmbz_notify' ) ) {
        rmbz_notify( (string) $p->artist_telemovel, 'editor_files_delivered', [
            'nome_artista' => (string) ( $p->user_nome ?: 'Artista' ),
            'musica'       => (string) $p->project_title,
            'ficheiros'    => (string) $d['files'],
            'tamanho'      => $gb_txt,
            'qualidade'    => (string) $d['quality'],
            'site_name'    => get_bloginfo( 'name' ),
        ] );
    }
    if ( function_exists( 'rmbz_pchat_append_change_log' ) ) {
        rmbz_pchat_append_change_log( (int) $project_id, [
            'who'  => (string) $by,
            'what' => '🚚 Ficheiros ENTREGUES ao editor externo — 1MZ aguarda a edição para aprovar o vídeo final',
        ] );
    }
    return true;
}

/** [1.5.411] admin-post: registar os ficheiros para o editor (passo 1). */
function rmbz_editor_delivery_handle_info() {
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );
    $project_id = isset( $_POST['project_id'] ) ? absint( $_POST['project_id'] ) : 0;
    check_admin_referer( 'rmbz_editor_delivery_' . $project_id );
    $user = wp_get_current_user();
    $r = rmbz_editor_delivery_set_info(
        $project_id,
        isset( $_POST['ed_files'] ) ? (int) $_POST['ed_files'] : 0,
        isset( $_POST['ed_size_gb'] ) ? (float) str_replace( ',', '.', (string) $_POST['ed_size_gb'] ) : 0,
        isset( $_POST['ed_quality'] ) ? wp_unslash( $_POST['ed_quality'] ) : '',
        $user && $user->exists() ? $user->display_name : 'admin'
    );
    $flag = $r === true ? 'ed_info_saved=1' : 'ed_error=' . rawurlencode( (string) $r );
    wp_safe_redirect( admin_url( 'admin.php?page=rmbz-projetos&action=edit&id=' . $project_id . '&' . $flag ) );
    exit;
}
add_action( 'admin_post_rmbz_editor_delivery_info', 'rmbz_editor_delivery_handle_info' );

/** [1.5.411] admin-post: marcar entrega ao editor (passo 2). */
function rmbz_editor_delivery_handle_done() {
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );
    $project_id = isset( $_POST['project_id'] ) ? absint( $_POST['project_id'] ) : 0;
    check_admin_referer( 'rmbz_editor_delivery_' . $project_id );
    $user = wp_get_current_user();
    $r = rmbz_editor_delivery_mark_delivered( $project_id, $user && $user->exists() ? $user->display_name : 'admin' );
    $flag = $r === true ? 'ed_delivered=1' : 'ed_error=' . rawurlencode( (string) $r );
    wp_safe_redirect( admin_url( 'admin.php?page=rmbz-projetos&action=edit&id=' . $project_id . '&' . $flag ) );
    exit;
}
add_action( 'admin_post_rmbz_editor_delivery_done', 'rmbz_editor_delivery_handle_done' );

/**
 * [1.6.118.100] Aplica a taxa de vistoria ao ORÇAMENTO do projecto.
 *
 * Adiciona um item ao scenario_details (categoria "Vistoria 1MZ") por cada
 * pessoa externa pedida pelo artista que ainda não tenha taxa aplicada.
 * O item é obrigatório (must_pay=1) e entra no "A pagar à 1MZ".
 *
 * Idempotente: usa extra_data['team_fee_applied'] = [member_id => true] para
 * não duplicar. Marca os items com 'team_fee_for' = member_id.
 */
function rmbz_team_handle_apply_fee() {
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );
    // [1.6.370] Aceita GET (valores sugeridos) ou POST (valores manuais por pessoa).
    $project_id = isset( $_REQUEST['project_id'] ) ? absint( $_REQUEST['project_id'] ) : 0;
    check_admin_referer( 'rmbz_team_apply_fee_' . $project_id );
    if ( $project_id <= 0 ) wp_die( 'Projecto inválido.' );

    // [1.6.370] Valores manuais por pessoa: fee_manual[member_id] = valor MT.
    $manual = ( isset( $_POST['fee_manual'] ) && is_array( $_POST['fee_manual'] ) )
        ? $_POST['fee_manual'] : [];

    global $wpdb;
    $table = $wpdb->prefix . 'rmbz_artist_projects';
    $row = $wpdb->get_row( $wpdb->prepare(
        "SELECT extra_data, scenario_details, scenario_cost, amount_paid, is_paid FROM $table WHERE id = %d", $project_id
    ) );
    if ( ! $row ) wp_die( 'Projecto não encontrado.' );

    $extra   = ! empty( $row->extra_data ) ? json_decode( $row->extra_data, true ) : [];
    $details = ! empty( $row->scenario_details ) ? json_decode( $row->scenario_details, true ) : [];
    if ( ! is_array( $extra ) )   $extra = [];
    if ( ! is_array( $details ) ) $details = [];

    $diag = rmbz_team_validate_project( $extra );
    $applied = isset( $extra['team_fee_applied'] ) && is_array( $extra['team_fee_applied'] )
        ? $extra['team_fee_applied'] : [];

    $added = 0;
    foreach ( $diag['external_by_artist'] as $m ) {
        $mid = (int) $m->id;
        if ( ! empty( $applied[ $mid ] ) ) continue; // já aplicada

        // [1.5.408] UMA vistoria POR DEPARTAMENTO dos cargos que a pessoa vai
        // exercer neste projeto (Realizador+Editor = 2 vistorias). Valores
        // manuais por departamento: fee_manual[mid][dept]; compat com o
        // formato antigo fee_manual[mid] = valor único (soma numa linha só).
        $fee_lines = rmbz_team_member_fee_lines( $m, $extra );

        if ( isset( $manual[ $mid ] ) && ! is_array( $manual[ $mid ] ) && is_numeric( $manual[ $mid ] ) ) {
            // Formato antigo: um valor total → uma linha única.
            $details[] = [
                'cat'            => 'Vistoria 1MZ',
                'desc'           => 'Vistoria de ' . $m->name,
                'cost'           => max( 0.0, (float) $manual[ $mid ] ),
                'checked'        => true,
                'must_pay'       => 1,
                'artist_assumes' => 0,
                'decided'        => 1,
                'team_fee_for'   => $mid,
            ];
        } else {
            foreach ( $fee_lines as $dk => $line ) {
                $line_fee = (float) $line['fee'];
                if ( isset( $manual[ $mid ][ $dk ] ) && is_numeric( $manual[ $mid ][ $dk ] ) ) {
                    $line_fee = max( 0.0, (float) $manual[ $mid ][ $dk ] );
                }
                $desc = 'Vistoria de ' . $m->name . ' — ' . $line['label'];
                if ( ! empty( $line['roles'] ) ) {
                    $desc .= ' (' . implode( ', ', $line['roles'] ) . ')';
                }
                $details[] = [
                    'cat'            => 'Vistoria 1MZ',
                    'desc'           => $desc,
                    'cost'           => $line_fee,
                    'checked'        => true,
                    'must_pay'       => 1,   // obrigatório → entra no "A pagar à 1MZ"
                    'artist_assumes' => 0,
                    'decided'        => 1,
                    'team_fee_for'   => $mid, // marca para futura remoção/auditoria
                    'team_fee_dept'  => $dk,
                ];
            }
        }
        $applied[ $mid ] = true;
        $added++;
    }

    if ( $added > 0 ) {
        $extra['team_fee_applied'] = $applied;
        // Recalcular scenario_cost (soma de todos os items)
        $total = 0.0;
        foreach ( $details as $it ) { $total += isset( $it['cost'] ) ? (float) $it['cost'] : 0.0; }

        // [1.5.410] BUGFIX Controlo Financeiro: a vistoria aumenta o valor
        // OBRIGATÓRIO a pagar à 1MZ — se o projeto estava 100% pago (is_paid),
        // deixa de estar. Antes o is_paid ficava intacto e o app do artista
        // mostrava "PAGO · Restante 0 MT" mesmo com a vistoria por pagar.
        $oblig = 0.0;
        foreach ( $details as $it ) {
            if ( ! is_array( $it ) ) continue;
            $mp = isset( $it['must_pay'] ) ? (int) $it['must_pay'] : 1;
            $aa = ! empty( $it['artist_assumes'] );
            if ( $mp === 1 || ! $aa ) $oblig += isset( $it['cost'] ) ? (float) $it['cost'] : 0.0;
        }
        $paid_now = ( (float) ( $row->amount_paid ?? 0 ) + 0.009 ) >= $oblig ? 1 : 0;

        $wpdb->update( $table, [
            'scenario_details' => wp_json_encode( $details ),
            'scenario_cost'    => $total,
            'is_paid'          => $paid_now,
            'extra_data'       => wp_json_encode( $extra ),
        ], [ 'id' => $project_id ] );

        if ( (int) ( $row->is_paid ?? 0 ) === 1 && $paid_now === 0 && function_exists( 'rmbz_pchat_append_change_log' ) ) {
            rmbz_pchat_append_change_log( $project_id, [
                'who'  => function_exists( 'rmbz_pchat_current_admin_who' ) ? rmbz_pchat_current_admin_who() : 'admin',
                'what' => sprintf( '💰 Vistoria aplicada — o projeto deixou de estar totalmente pago (falta %s MT)',
                    number_format( max( 0, $oblig - (float) ( $row->amount_paid ?? 0 ) ), 2, ',', '.' ) ),
            ] );
        }
    }

    wp_safe_redirect( admin_url( 'admin.php?page=rmbz-projetos&action=edit&id=' . $project_id . '&fee_applied=' . $added ) );
    exit;
}
add_action( 'admin_post_rmbz_team_apply_fee', 'rmbz_team_handle_apply_fee' );

// =============================================================================
// HANDLERS admin-post (guardar / apagar membro)
// =============================================================================

/**
 * Guarda (cria ou actualiza) um membro do banco de pessoas.
 */
function rmbz_team_handle_save_member() {
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );
    check_admin_referer( 'rmbz_team_save_member_nonce' );

    $id = isset( $_POST['member_id'] ) ? absint( $_POST['member_id'] ) : 0;
    $data = [
        'name'          => $_POST['name'] ?? '',
        'department'    => $_POST['department'] ?? '',
        'origin'        => $_POST['origin'] ?? '1mz',
        'role'          => $_POST['role'] ?? '',
        // [1.5.408] Múltiplos cargos: chips "dept|Cargo" + texto livre.
        'roles'         => ( isset( $_POST['roles'] ) && is_array( $_POST['roles'] ) ) ? wp_unslash( $_POST['roles'] ) : [],
        'roles_free'    => isset( $_POST['roles_free'] ) ? wp_unslash( (string) $_POST['roles_free'] ) : '',
        'specialty'     => $_POST['specialty'] ?? '',
        'bio'           => $_POST['bio'] ?? '',
        'photo_url'     => $_POST['photo_url'] ?? '',
        'socials'       => isset( $_POST['socials'] ) && is_array( $_POST['socials'] ) ? $_POST['socials'] : [],
        'contact_phone' => $_POST['contact_phone'] ?? '',
        'contact_email' => $_POST['contact_email'] ?? '',
    ];
    $saved_id = rmbz_team_save( $data, $id );

    // [1.5.412] PIN inicial do portal da equipa: se o admin preencheu o campo,
    // (re)define o PIN — o membro é obrigado a trocar no primeiro login.
    $pin_flag = '';
    $access_pin = isset( $_POST['access_pin'] ) ? trim( (string) wp_unslash( $_POST['access_pin'] ) ) : '';
    if ( $saved_id && $access_pin !== '' ) {
        $r = rmbz_team_set_pin( (int) $saved_id, $access_pin, true );
        $pin_flag = $r === true ? '&pin_set=1' : '&pin_error=' . rawurlencode( (string) $r );
    }

    wp_safe_redirect( admin_url( 'admin.php?page=rmbz-projetos&action=team&saved=1' . $pin_flag ) );
    exit;
}
add_action( 'admin_post_rmbz_team_save_member', 'rmbz_team_handle_save_member' );

/**
 * Apaga (soft delete) um membro.
 */
function rmbz_team_handle_delete_member() {
    if ( ! current_user_can( 'manage_options' ) ) wp_die( 'Sem permissão.' );
    $id = isset( $_GET['member_id'] ) ? absint( $_GET['member_id'] ) : 0;
    check_admin_referer( 'rmbz_team_delete_' . $id );

    if ( $id > 0 ) rmbz_team_delete( $id );

    wp_safe_redirect( admin_url( 'admin.php?page=rmbz-projetos&action=team&deleted=1' ) );
    exit;
}
add_action( 'admin_post_rmbz_team_delete_member', 'rmbz_team_handle_delete_member' );
