package models

import (
	"fmt"
	"time"

	"odoo-go/pkg/orm"
)

// initProjectProjectExtension extends project.project with additional fields and methods.
// Mirrors: odoo/addons/project/models/project_project.py (additional fields)
//          odoo/addons/hr_timesheet/models/project_project.py (timesheet fields)
func initProjectProjectExtension() {
	proj := orm.ExtendModel("project.project")

	proj.AddFields(
		orm.One2many("update_ids", "project.update", "project_id", orm.FieldOpts{
			String: "Updates",
		}),
		orm.One2many("milestone_ids", "project.milestone", "project_id", orm.FieldOpts{
			String: "Milestones",
		}),
		orm.Selection("last_update_status", []orm.SelectionItem{
			{Value: "on_track", Label: "On Track"},
			{Value: "at_risk", Label: "At Risk"},
			{Value: "off_track", Label: "Off Track"},
			{Value: "on_hold", Label: "On Hold"},
		}, orm.FieldOpts{String: "Status", Compute: "_compute_last_update_status"}),
		orm.Float("total_timesheet_hours", orm.FieldOpts{
			String: "Total Timesheet Hours", Compute: "_compute_total_timesheet_hours",
		}),
		orm.Boolean("allow_timesheets", orm.FieldOpts{String: "Timesheets", Default: true}),
		orm.Integer("update_count", orm.FieldOpts{
			String: "Update Count", Compute: "_compute_update_count",
		}),
		orm.Integer("milestone_count", orm.FieldOpts{
			String: "Milestone Count", Compute: "_compute_milestone_count",
		}),
		orm.Integer("milestone_count_reached", orm.FieldOpts{
			String: "Milestones Reached", Compute: "_compute_milestone_count_reached",
		}),
		orm.Float("allocated_hours", orm.FieldOpts{String: "Allocated Hours"}),
		orm.Float("remaining_hours", orm.FieldOpts{
			String: "Remaining Hours", Compute: "_compute_remaining_hours",
		}),
		orm.Float("progress", orm.FieldOpts{
			String: "Progress (%)", Compute: "_compute_progress",
		}),
		orm.Selection("privacy_visibility", []orm.SelectionItem{
			{Value: "followers", Label: "Invited internal users"},
			{Value: "employees", Label: "All internal users"},
			{Value: "portal", Label: "Invited portal users and all internal users"},
		}, orm.FieldOpts{String: "Visibility", Default: "portal"}),
		orm.Many2one("analytic_account_id", "account.analytic.account", orm.FieldOpts{
			String: "Analytic Account",
		}),
		orm.Many2many("tag_ids", "project.tags", orm.FieldOpts{String: "Tags"}),
		orm.One2many("task_ids", "project.task", "project_id", orm.FieldOpts{String: "Tasks"}),
	)

	// -- _compute_task_count --
	// Mirrors: odoo/addons/project/models/project_project.py Project._compute_task_count()
	proj.RegisterCompute("task_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var count int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM project_task WHERE project_id = $1 AND active = true`, projID).Scan(&count)
		return orm.Values{"task_count": count}, nil
	})

	// -- _compute_last_update_status --
	// Mirrors: odoo/addons/project/models/project_project.py Project._compute_last_update_status()
	proj.RegisterCompute("last_update_status", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var status *string
		env.Tx().QueryRow(env.Ctx(),
			`SELECT status FROM project_update
			 WHERE project_id = $1
			 ORDER BY date DESC, id DESC LIMIT 1`, projID).Scan(&status)
		if status != nil {
			return orm.Values{"last_update_status": *status}, nil
		}
		return orm.Values{"last_update_status": "on_track"}, nil
	})

	// -- _compute_total_timesheet_hours --
	// Mirrors: odoo/addons/hr_timesheet/models/project_project.py Project._compute_total_timesheet_time()
	proj.RegisterCompute("total_timesheet_hours", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var hours float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE project_id = $1`, projID).Scan(&hours)
		return orm.Values{"total_timesheet_hours": hours}, nil
	})

	// -- _compute_update_count --
	proj.RegisterCompute("update_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var count int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM project_update WHERE project_id = $1`, projID).Scan(&count)
		return orm.Values{"update_count": count}, nil
	})

	// -- _compute_milestone_count --
	proj.RegisterCompute("milestone_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var count int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM project_milestone WHERE project_id = $1`, projID).Scan(&count)
		return orm.Values{"milestone_count": count}, nil
	})

	// -- _compute_milestone_count_reached --
	proj.RegisterCompute("milestone_count_reached", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var count int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM project_milestone WHERE project_id = $1 AND is_reached = true`, projID).Scan(&count)
		return orm.Values{"milestone_count_reached": count}, nil
	})

	// -- _compute_remaining_hours --
	proj.RegisterCompute("remaining_hours", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var allocated, timesheet float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(allocated_hours, 0) FROM project_project WHERE id = $1`, projID).Scan(&allocated)
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE project_id = $1`, projID).Scan(&timesheet)
		remaining := allocated - timesheet
		if remaining < 0 {
			remaining = 0
		}
		return orm.Values{"remaining_hours": remaining}, nil
	})

	// -- _compute_progress --
	proj.RegisterCompute("progress", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		projID := rs.IDs()[0]
		var allocated, timesheet float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(allocated_hours, 0) FROM project_project WHERE id = $1`, projID).Scan(&allocated)
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE project_id = $1`, projID).Scan(&timesheet)
		pct := float64(0)
		if allocated > 0 {
			pct = timesheet / allocated * 100
			if pct > 100 {
				pct = 100
			}
		}
		return orm.Values{"progress": pct}, nil
	})

	// action_view_tasks: Open tasks of this project.
	// Mirrors: odoo/addons/project/models/project_project.py Project.action_view_tasks()
	proj.RegisterMethod("action_view_tasks", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		projID := rs.IDs()[0]
		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "project.task",
			"view_mode": "kanban,list,form",
			"views":     [][]interface{}{{nil, "kanban"}, {nil, "list"}, {nil, "form"}},
			"domain":    []interface{}{[]interface{}{"project_id", "=", projID}},
			"target":    "current",
			"name":      "Tasks",
			"context":   map[string]interface{}{"default_project_id": projID},
		}, nil
	})

	// action_view_milestones: Open milestones of this project.
	// Mirrors: odoo/addons/project/models/project_project.py Project.action_view_milestones()
	proj.RegisterMethod("action_view_milestones", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		projID := rs.IDs()[0]
		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "project.milestone",
			"view_mode": "list,form",
			"views":     [][]interface{}{{nil, "list"}, {nil, "form"}},
			"domain":    []interface{}{[]interface{}{"project_id", "=", projID}},
			"target":    "current",
			"name":      "Milestones",
			"context":   map[string]interface{}{"default_project_id": projID},
		}, nil
	})

	// action_view_updates: Open project updates.
	proj.RegisterMethod("action_view_updates", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		projID := rs.IDs()[0]
		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "project.update",
			"view_mode": "list,form",
			"views":     [][]interface{}{{nil, "list"}, {nil, "form"}},
			"domain":    []interface{}{[]interface{}{"project_id", "=", projID}},
			"target":    "current",
			"name":      "Updates",
			"context":   map[string]interface{}{"default_project_id": projID},
		}, nil
	})

	// action_view_timesheets: Open timesheet entries for this project.
	// Mirrors: odoo/addons/hr_timesheet/models/project_project.py
	proj.RegisterMethod("action_view_timesheets", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		projID := rs.IDs()[0]
		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "account.analytic.line",
			"view_mode": "list,form",
			"views":     [][]interface{}{{nil, "list"}, {nil, "form"}},
			"domain":    []interface{}{[]interface{}{"project_id", "=", projID}},
			"target":    "current",
			"name":      "Timesheets",
			"context":   map[string]interface{}{"default_project_id": projID},
		}, nil
	})

	// action_get_project_profitability: Get profitability data.
	// Mirrors: odoo/addons/project/models/project_profitability.py
	proj.RegisterMethod("action_get_project_profitability", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		projID := rs.IDs()[0]

		// Compute costs from timesheets (hours * employee cost)
		var timesheetCost float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(aal.unit_amount * COALESCE(he.timesheet_cost, 0)), 0)
			 FROM account_analytic_line aal
			 LEFT JOIN hr_employee he ON he.id = aal.employee_id
			 WHERE aal.project_id = $1`, projID).Scan(&timesheetCost)

		// Compute revenue from linked SOs (if project has partner)
		var revenue float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(so.amount_total::float8), 0) FROM sale_order so
			 WHERE so.state IN ('sale', 'done')
			 AND so.partner_id = (SELECT partner_id FROM project_project WHERE id = $1)
			 AND so.partner_id IS NOT NULL`, projID).Scan(&revenue)

		return map[string]interface{}{
			"timesheet_cost": timesheetCost,
			"revenue":        revenue,
			"margin":         revenue - timesheetCost,
		}, nil
	})
}

// initProjectTaskExtension extends project.task with additional fields and methods.
// Mirrors: odoo/addons/project/models/project_task.py (additional fields)
//          odoo/addons/hr_timesheet/models/project_task.py (timesheet fields)
func initProjectTaskExtension() {
	task := orm.ExtendModel("project.task")

	// Note: parent_id, child_ids, milestone_id, tag_ids, depend_ids already exist
	task.AddFields(
		orm.Float("planned_hours", orm.FieldOpts{String: "Initially Planned Hours"}),
		orm.Float("effective_hours", orm.FieldOpts{
			String: "Hours Spent", Compute: "_compute_effective_hours",
		}),
		orm.Float("remaining_hours", orm.FieldOpts{
			String: "Remaining Hours", Compute: "_compute_remaining_hours",
		}),
		orm.Float("progress", orm.FieldOpts{
			String: "Progress (%)", Compute: "_compute_progress",
		}),
		orm.Float("subtask_effective_hours", orm.FieldOpts{
			String: "Sub-task Hours", Compute: "_compute_subtask_effective_hours",
		}),
		orm.Float("total_hours_spent", orm.FieldOpts{
			String: "Total Hours", Compute: "_compute_total_hours_spent",
		}),
		orm.Date("date_last_stage_update", orm.FieldOpts{String: "Last Stage Update"}),
		orm.One2many("timesheet_ids", "account.analytic.line", "task_id", orm.FieldOpts{
			String: "Timesheets",
		}),
		orm.Integer("timesheet_count", orm.FieldOpts{
			String: "Timesheet Count", Compute: "_compute_timesheet_count",
		}),
		orm.Char("email_from", orm.FieldOpts{String: "Email From"}),
		orm.Boolean("allow_timesheets", orm.FieldOpts{String: "Allow Timesheets"}),
		orm.Integer("working_days_close", orm.FieldOpts{
			String: "Working Days to Close", Compute: "_compute_working_days_close",
		}),
		orm.Integer("working_days_open", orm.FieldOpts{
			String: "Working Days to Assign", Compute: "_compute_working_days_open",
		}),
		orm.Float("overtime", orm.FieldOpts{
			String: "Overtime", Compute: "_compute_overtime",
		}),
		orm.Many2one("sale_order_id", "sale.order", orm.FieldOpts{String: "Sales Order"}),
		orm.Many2one("sale_line_id", "sale.order.line", orm.FieldOpts{String: "Sales Order Item"}),
		orm.Many2one("analytic_account_id", "account.analytic.account", orm.FieldOpts{
			String: "Analytic Account",
		}),
	)

	// -- _compute_effective_hours: Sum of timesheet hours on this task. --
	// Mirrors: odoo/addons/hr_timesheet/models/project_task.py Task._compute_effective_hours()
	task.RegisterCompute("effective_hours", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var hours float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE task_id = $1`, taskID).Scan(&hours)
		return orm.Values{"effective_hours": hours}, nil
	})

	// -- _compute_remaining_hours --
	// Mirrors: odoo/addons/hr_timesheet/models/project_task.py Task._compute_remaining_hours()
	task.RegisterCompute("remaining_hours", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var planned, effective float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(planned_hours, 0) FROM project_task WHERE id = $1`, taskID).Scan(&planned)
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE task_id = $1`, taskID).Scan(&effective)
		remaining := planned - effective
		return orm.Values{"remaining_hours": remaining}, nil
	})

	// -- _compute_progress --
	// Mirrors: odoo/addons/hr_timesheet/models/project_task.py Task._compute_progress()
	task.RegisterCompute("progress", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var planned float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(planned_hours, 0) FROM project_task WHERE id = $1`, taskID).Scan(&planned)
		if planned <= 0 {
			return orm.Values{"progress": float64(0)}, nil
		}
		var effective float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE task_id = $1`, taskID).Scan(&effective)
		pct := effective / planned * 100
		if pct > 100 {
			pct = 100
		}
		return orm.Values{"progress": pct}, nil
	})

	// -- _compute_subtask_effective_hours --
	task.RegisterCompute("subtask_effective_hours", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var hours float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(aal.unit_amount), 0)
			 FROM account_analytic_line aal
			 JOIN project_task pt ON pt.id = aal.task_id
			 WHERE pt.parent_id = $1`, taskID).Scan(&hours)
		return orm.Values{"subtask_effective_hours": hours}, nil
	})

	// -- _compute_total_hours_spent: own hours + subtask hours --
	task.RegisterCompute("total_hours_spent", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var ownHours, subtaskHours float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE task_id = $1`, taskID).Scan(&ownHours)
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(aal.unit_amount), 0)
			 FROM account_analytic_line aal
			 JOIN project_task pt ON pt.id = aal.task_id
			 WHERE pt.parent_id = $1`, taskID).Scan(&subtaskHours)
		return orm.Values{"total_hours_spent": ownHours + subtaskHours}, nil
	})

	// -- _compute_timesheet_count --
	task.RegisterCompute("timesheet_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var count int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM account_analytic_line WHERE task_id = $1`, taskID).Scan(&count)
		return orm.Values{"timesheet_count": count}, nil
	})

	// -- _compute_working_days_close --
	// Mirrors: odoo/addons/project/models/project_task.py Task._compute_elapsed()
	task.RegisterCompute("working_days_close", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var state string
		var createDate *time.Time
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(state, 'open'), create_date FROM project_task WHERE id = $1`, taskID).Scan(&state, &createDate)
		if state != "done" || createDate == nil {
			return orm.Values{"working_days_close": 0}, nil
		}
		days := int(time.Since(*createDate).Hours() / 24)
		return orm.Values{"working_days_close": days}, nil
	})

	// -- _compute_working_days_open --
	task.RegisterCompute("working_days_open", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var dateAssign *time.Time
		var createDate *time.Time
		env.Tx().QueryRow(env.Ctx(),
			`SELECT date_assign, create_date FROM project_task WHERE id = $1`, taskID).Scan(&dateAssign, &createDate)
		if dateAssign == nil || createDate == nil {
			return orm.Values{"working_days_open": 0}, nil
		}
		days := int(dateAssign.Sub(*createDate).Hours() / 24)
		if days < 0 {
			days = 0
		}
		return orm.Values{"working_days_open": days}, nil
	})

	// -- _compute_overtime --
	task.RegisterCompute("overtime", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var planned, effective float64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(planned_hours, 0) FROM project_task WHERE id = $1`, taskID).Scan(&planned)
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COALESCE(SUM(unit_amount), 0) FROM account_analytic_line
			 WHERE task_id = $1`, taskID).Scan(&effective)
		overtime := effective - planned
		if overtime < 0 {
			overtime = 0
		}
		return orm.Values{"overtime": overtime}, nil
	})

	// action_view_timesheets: Open timesheets for this task.
	task.RegisterMethod("action_view_timesheets", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		taskID := rs.IDs()[0]
		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "account.analytic.line",
			"view_mode": "list,form",
			"views":     [][]interface{}{{nil, "list"}, {nil, "form"}},
			"domain":    []interface{}{[]interface{}{"task_id", "=", taskID}},
			"target":    "current",
			"name":      "Timesheets",
			"context":   map[string]interface{}{"default_task_id": taskID},
		}, nil
	})

	// action_open_parent_task: Open parent task form.
	task.RegisterMethod("action_open_parent_task", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		taskID := rs.IDs()[0]
		var parentID *int64
		env.Tx().QueryRow(env.Ctx(),
			`SELECT parent_id FROM project_task WHERE id = $1`, taskID).Scan(&parentID)
		if parentID == nil {
			return nil, fmt.Errorf("task has no parent")
		}
		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "project.task",
			"res_id":    *parentID,
			"view_mode": "form",
			"target":    "current",
		}, nil
	})

	// action_subtask_view: Open subtasks list.
	task.RegisterMethod("action_subtask_view", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		taskID := rs.IDs()[0]
		return map[string]interface{}{
			"type":      "ir.actions.act_window",
			"res_model": "project.task",
			"view_mode": "list,form",
			"views":     [][]interface{}{{nil, "list"}, {nil, "form"}},
			"domain":    []interface{}{[]interface{}{"parent_id", "=", taskID}},
			"target":    "current",
			"name":      "Sub-tasks",
			"context":   map[string]interface{}{"default_parent_id": taskID},
		}, nil
	})

	// action_assign_to_me: Assign the task to the current user.
	task.RegisterMethod("action_assign_to_me", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		uid := env.UID()
		for _, taskID := range rs.IDs() {
			env.Tx().Exec(env.Ctx(),
				`INSERT INTO project_task_res_users_rel (project_task_id, res_users_id)
				 VALUES ($1, $2) ON CONFLICT DO NOTHING`, taskID, uid)
			env.Tx().Exec(env.Ctx(),
				`UPDATE project_task SET date_assign = NOW()
				 WHERE id = $1 AND date_assign IS NULL`, taskID)
		}
		return true, nil
	})

	// action_set_stage: Change the task stage.
	// Mirrors: odoo/addons/project/models/project_task.py Task.write() stage tracking
	task.RegisterMethod("action_set_stage", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		if len(args) < 1 {
			return nil, fmt.Errorf("stage_id required")
		}
		env := rs.Env()
		stageID, _ := args[0].(float64)
		for _, taskID := range rs.IDs() {
			env.Tx().Exec(env.Ctx(),
				`UPDATE project_task SET stage_id = $1, date_last_stage_update = NOW()
				 WHERE id = $2`, int64(stageID), taskID)
		}
		return true, nil
	})
}

// initProjectMilestoneExtension extends project.milestone with additional fields.
// Mirrors: odoo/addons/project/models/project_milestone.py (additional fields)
func initProjectMilestoneExtension() {
	ms := orm.ExtendModel("project.milestone")

	ms.AddFields(
		orm.Date("reached_date", orm.FieldOpts{String: "Reached Date"}),
		orm.Integer("task_count", orm.FieldOpts{
			String: "Task Count", Compute: "_compute_task_count",
		}),
		orm.Float("progress", orm.FieldOpts{
			String: "Progress (%)", Compute: "_compute_progress",
		}),
		orm.Integer("color", orm.FieldOpts{String: "Color Index"}),
		orm.Many2one("sale_line_id", "sale.order.line", orm.FieldOpts{String: "Sales Order Item"}),
	)

	// action_toggle_reached: Toggle the reached status.
	ms.RegisterMethod("action_toggle_reached", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		env := rs.Env()
		for _, msID := range rs.IDs() {
			var isReached bool
			env.Tx().QueryRow(env.Ctx(),
				`SELECT COALESCE(is_reached, false) FROM project_milestone WHERE id = $1`, msID).Scan(&isReached)
			if isReached {
				env.Tx().Exec(env.Ctx(),
					`UPDATE project_milestone SET is_reached = false, reached_date = NULL WHERE id = $1`, msID)
			} else {
				env.Tx().Exec(env.Ctx(),
					`UPDATE project_milestone SET is_reached = true, reached_date = CURRENT_DATE WHERE id = $1`, msID)
			}
		}
		return true, nil
	})

	// _compute_task_count
	ms.RegisterCompute("task_count", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		msID := rs.IDs()[0]
		var count int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM project_task WHERE milestone_id = $1`, msID).Scan(&count)
		return orm.Values{"task_count": count}, nil
	})

	// _compute_progress: % of tasks that are done out of total linked tasks.
	ms.RegisterCompute("progress", func(rs *orm.Recordset) (orm.Values, error) {
		env := rs.Env()
		msID := rs.IDs()[0]
		var total, done int
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM project_task WHERE milestone_id = $1`, msID).Scan(&total)
		env.Tx().QueryRow(env.Ctx(),
			`SELECT COUNT(*) FROM project_task WHERE milestone_id = $1 AND state = 'done'`, msID).Scan(&done)
		pct := float64(0)
		if total > 0 {
			pct = float64(done) / float64(total) * 100
		}
		return orm.Values{"progress": pct}, nil
	})
}

// initProjectTaskRecurrence registers project.task.recurrence.
// Mirrors: odoo/addons/project/models/project_task_recurrence.py
func initProjectTaskRecurrence() {
	m := orm.NewModel("project.task.recurrence", orm.ModelOpts{
		Description: "Task Recurrence",
	})

	m.AddFields(
		orm.Many2many("task_ids", "project.task", orm.FieldOpts{String: "Tasks"}),
		orm.Selection("repeat_interval", []orm.SelectionItem{
			{Value: "daily", Label: "Days"},
			{Value: "weekly", Label: "Weeks"},
			{Value: "monthly", Label: "Months"},
			{Value: "yearly", Label: "Years"},
		}, orm.FieldOpts{String: "Repeat Every", Default: "weekly"}),
		orm.Integer("repeat_number", orm.FieldOpts{String: "Repeat", Default: 1}),
		orm.Selection("repeat_type", []orm.SelectionItem{
			{Value: "forever", Label: "Forever"},
			{Value: "until", Label: "Until"},
			{Value: "after", Label: "Number of Repetitions"},
		}, orm.FieldOpts{String: "Until", Default: "forever"}),
		orm.Date("repeat_until", orm.FieldOpts{String: "End Date"}),
		orm.Integer("repeat_on_month", orm.FieldOpts{String: "Day of Month", Default: 1}),
		orm.Boolean("mon", orm.FieldOpts{String: "Monday"}),
		orm.Boolean("tue", orm.FieldOpts{String: "Tuesday"}),
		orm.Boolean("wed", orm.FieldOpts{String: "Wednesday"}),
		orm.Boolean("thu", orm.FieldOpts{String: "Thursday"}),
		orm.Boolean("fri", orm.FieldOpts{String: "Friday"}),
		orm.Boolean("sat", orm.FieldOpts{String: "Saturday"}),
		orm.Boolean("sun", orm.FieldOpts{String: "Sunday"}),
		orm.Integer("recurrence_left", orm.FieldOpts{String: "Recurrences Left"}),
		orm.Date("next_recurrence_date", orm.FieldOpts{String: "Next Recurrence Date"}),
	)
}

// initProjectSharingWizard registers a wizard for sharing projects with external users.
// Mirrors: odoo/addons/project/wizard/project_share_wizard.py
func initProjectSharingWizard() {
	m := orm.NewModel("project.share.wizard", orm.ModelOpts{
		Description: "Project Sharing Wizard",
		Type:        orm.ModelTransient,
	})

	m.AddFields(
		orm.Many2one("project_id", "project.project", orm.FieldOpts{
			String: "Project", Required: true,
		}),
		orm.Many2many("partner_ids", "res.partner", orm.FieldOpts{String: "Recipients"}),
		orm.Selection("access_mode", []orm.SelectionItem{
			{Value: "read", Label: "Read"},
			{Value: "edit", Label: "Edit"},
		}, orm.FieldOpts{String: "Access Mode", Default: "read"}),
		orm.Text("note", orm.FieldOpts{String: "Note"}),
	)

	// action_share: Share the project with selected partners.
	m.RegisterMethod("action_share", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) {
		// Placeholder: in full Odoo this would create portal access rules
		return true, nil
	})
}
