<?
namespace factory;

class Tasks
{
  private const MIN_WORK_LOG_SECONDS = 60;

  private static function isEnabled( $value )
  {
    if ( is_bool( $value ) )
      return $value;

    if ( is_numeric( $value ) )
      return (int)$value === 1;

    if ( is_string( $value ) )
      return in_array( strtolower( trim( $value ) ), [ '1', 'on', 'true', 'yes' ], true );

    return false;
  }

  public static $statuses = [ 0 => 'nowe', 3 => 'do rozliczenia', 5 => 'do zrobienia', 1 => 'do sprawdzenia', 2 => 'zamknięte' ];
  public static $priorities = [ 0 => 'niski', 1 => 'normalny', 2 => 'wysoki', 3 => 'pilny' ];

  static public function filtr_details( $filtr_id ) {
    global $mdb;
    return $mdb -> get( 'tasks_filtrs', '*', [ 'id' => $filtr_id ] );
  }


  static public function get_priorities()
  {
    return self::$priorities;
  }

  static public function task_change_dates( $task_id, $date_start, $date_end )
  {
    global $mdb;

    if ( !$task_id )
      return false;

    return $mdb -> update( 'tasks', [
      'date_start' => $date_start,
      'date_end' => $date_end
    ], [
      'id' => $task_id
    ] );
  }

  static public function parent_tasks( $user_id )
  {
    global $mdb;

    if ( $user_id != 1 )
      $sql = ' AND ( id IN (SELECT task_id FROM task_user WHERE user_id = ' . $user_id . ') OR created_by = ' . $user_id . ' ) ';
    else
      $sql = '';

    $result = $mdb -> query( 'SELECT name, id, project_id, client_id FROM tasks WHERE deleted = 0 AND status != 2 ' . $sql . ' ORDER BY date_start IS NULL ASC, date_start ASC, o ASC, id DESC' ) -> fetchAll( \PDO::FETCH_ASSOC );
    foreach ( $result as $row )
    {
      $task['id'] = $row['id'];
      $task['name'] = htmlspecialchars( $row['name'] );
      $task['project_id'] = $row['project_id'];
      $task['client_id'] = $row['client_id'] ? (int)$row['client_id'] : 0;
      $task['client'] = $row['client_id'] ? \factory\Crm::get_client_name( (int)$row['client_id'] ) : null;
      $task['project'] = \factory\Projects::get_project_name( $row['project_id'] );
      $tasks[] = $task;
    }
    return $tasks;
  }

  static public function get_tasks_gantt( $user_id, $projects = null, $users = null ) {
    global $mdb;
    $data = [];

    if ( $users ) {
      $sql = ' AND t.id IN (SELECT task_id FROM task_user WHERE user_id IN (' . implode( ',', $users ) . ')) ';
    } else {
      $sql = '';
    }

    if ( $projects ) {
      $sql .= ' AND t.project_id IN (' . implode( ',', $projects ) . ') ';
    }

    if ( $user_id != 1 ) {
      $sql_query = 'SELECT '
                    . 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, t.parent_id, t.priority, pt.date_start AS parent_date_start, pt.date_end AS parent_date_end '
                    . 'FROM tasks AS t '
                    . 'LEFT JOIN tasks AS pt ON t.parent_id = pt.id '
                    . 'LEFT JOIN task_user AS tu ON t.id = tu.task_id '
                    . 'WHERE tu.user_id = ' . $user_id . ' AND t.status != 2 AND t.status != 3 AND t.status != 1 AND ((t.show_in_calendar = 1 AND t.date_start <= DATE_ADD(NOW(), INTERVAL 1 MONTH)) OR (t.parent_id IS NOT NULL AND COALESCE(t.date_start, pt.date_start) IS NOT NULL AND COALESCE(t.date_start, pt.date_start) <= DATE_ADD(NOW(), INTERVAL 1 MONTH))) ' . $sql . ' ORDER BY t.priority DESC, COALESCE(t.date_start, pt.date_start) ASC, COALESCE(t.date_end, pt.date_end) ASC, t.o ASC';
    } else {
      $sql_query = 'SELECT '
                      . 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, t.parent_id, t.priority, pt.date_start AS parent_date_start, pt.date_end AS parent_date_end '
                    . 'FROM tasks AS t '
                    . 'LEFT JOIN tasks AS pt ON t.parent_id = pt.id '
                    . 'WHERE t.status != 2 AND t.status != 3 AND t.status != 1 AND ((t.show_in_calendar = 1 AND t.date_start <= DATE_ADD(NOW(), INTERVAL 1 MONTH)) OR (t.parent_id IS NOT NULL AND COALESCE(t.date_start, pt.date_start) IS NOT NULL AND COALESCE(t.date_start, pt.date_start) <= DATE_ADD(NOW(), INTERVAL 1 MONTH))) ' . $sql . ' ORDER BY t.priority DESC, COALESCE(t.date_start, pt.date_start) ASC, COALESCE(t.date_end, pt.date_end) ASC, t.o ASC';
    }

    $tasks = $mdb -> query( $sql_query ) -> fetchAll( \PDO::FETCH_ASSOC );

    // If subtasks are present but their parent task is outside the base filter,
    // force-load missing parents so dependency tree expand/collapse can work.
    $tasks_by_id = [];
    foreach ( $tasks as $tmp_task )
      $tasks_by_id[ (int)$tmp_task['id'] ] = true;

    $pending_parent_ids = [];
    foreach ( $tasks as $tmp_task )
    {
      $parent_id = (int)$tmp_task['parent_id'];
      if ( $parent_id > 0 and !isset( $tasks_by_id[ $parent_id ] ) )
        $pending_parent_ids[ $parent_id ] = $parent_id;
    }

    while ( !empty( $pending_parent_ids ) )
    {
      $parent_ids = array_map( 'intval', array_values( $pending_parent_ids ) );
      $pending_parent_ids = [];

      $parents_query = 'SELECT '
                      . 't.id, t.name, t.date_start, t.date_end, t.status, t.client_id, t.parent_id, t.priority, pt.date_start AS parent_date_start, pt.date_end AS parent_date_end '
                      . 'FROM tasks AS t '
                      . 'LEFT JOIN tasks AS pt ON t.parent_id = pt.id '
                      . 'WHERE t.id IN (' . implode( ',', $parent_ids ) . ')';

      $parent_rows = $mdb -> query( $parents_query ) -> fetchAll( \PDO::FETCH_ASSOC );
      foreach ( $parent_rows as $parent_row )
      {
        $parent_row_id = (int)$parent_row['id'];
        if ( isset( $tasks_by_id[ $parent_row_id ] ) )
          continue;

        $tasks[] = $parent_row;
        $tasks_by_id[ $parent_row_id ] = true;

        $next_parent_id = (int)$parent_row['parent_id'];
        if ( $next_parent_id > 0 and !isset( $tasks_by_id[ $next_parent_id ] ) )
          $pending_parent_ids[ $next_parent_id ] = $next_parent_id;
      }
    }

    // Build a stable hierarchical order: parent task followed by its subtasks.
    $task_rows_by_id = [];
    $task_order_index = [];
    $task_children_map = [];

    foreach ( $tasks as $idx => $task_row )
    {
      $task_row_id = (int)$task_row['id'];
      if ( !$task_row_id )
        continue;

      if ( !isset( $task_rows_by_id[ $task_row_id ] ) )
      {
        $task_rows_by_id[ $task_row_id ] = $task_row;
        $task_order_index[ $task_row_id ] = $idx;
      }
    }

    foreach ( $task_rows_by_id as $task_row_id => $task_row )
    {
      $task_parent_id = (int)$task_row['parent_id'];
      if ( $task_parent_id > 0 )
      {
        if ( !isset( $task_children_map[ $task_parent_id ] ) )
          $task_children_map[ $task_parent_id ] = [];
        $task_children_map[ $task_parent_id ][] = $task_row_id;
      }
    }

    $root_ids = [];
    foreach ( $task_rows_by_id as $task_row_id => $task_row )
    {
      $task_parent_id = (int)$task_row['parent_id'];
      if ( $task_parent_id <= 0 or !isset( $task_rows_by_id[ $task_parent_id ] ) )
        $root_ids[] = $task_row_id;
    }

    // Effective priority for display: parent inherits the highest priority found
    // in its subtree (without changing DB value).
    $task_effective_priority = [];
    $effective_priority_stack = [];
    $compute_effective_priority = null;
    $compute_effective_priority = function( $task_id ) use ( &$compute_effective_priority, &$task_effective_priority, &$effective_priority_stack, $task_rows_by_id, $task_children_map ) {
      if ( isset( $task_effective_priority[ $task_id ] ) )
        return $task_effective_priority[ $task_id ];

      if ( isset( $effective_priority_stack[ $task_id ] ) )
        return isset( $task_rows_by_id[ $task_id ]['priority'] ) ? (int)$task_rows_by_id[ $task_id ]['priority'] : 0;

      $effective_priority_stack[ $task_id ] = true;

      $max_priority = isset( $task_rows_by_id[ $task_id ]['priority'] ) ? (int)$task_rows_by_id[ $task_id ]['priority'] : 0;
      if ( isset( $task_children_map[ $task_id ] ) )
      {
        foreach ( $task_children_map[ $task_id ] as $child_id )
        {
          $child_priority = (int)$compute_effective_priority( $child_id );
          if ( $child_priority > $max_priority )
            $max_priority = $child_priority;
        }
      }

      unset( $effective_priority_stack[ $task_id ] );
      $task_effective_priority[ $task_id ] = $max_priority;
      return $max_priority;
    };

    foreach ( array_keys( $task_rows_by_id ) as $task_id )
      $compute_effective_priority( $task_id );

    // Sort by effective priority first (DESC), then keep previous visual stability
    // by original SQL order (ASC) for equal priorities.
    $sort_by_effective_priority = function( $a, $b ) use ( $task_order_index, $task_effective_priority ) {
      $a_priority = isset( $task_effective_priority[ $a ] ) ? (int)$task_effective_priority[ $a ] : 0;
      $b_priority = isset( $task_effective_priority[ $b ] ) ? (int)$task_effective_priority[ $b ] : 0;
      if ( $a_priority !== $b_priority )
        return $a_priority > $b_priority ? -1 : 1;

      $a_idx = isset( $task_order_index[ $a ] ) ? $task_order_index[ $a ] : 999999;
      $b_idx = isset( $task_order_index[ $b ] ) ? $task_order_index[ $b ] : 999999;
      if ( $a_idx == $b_idx )
        return 0;
      return $a_idx < $b_idx ? -1 : 1;
    };

    usort( $root_ids, $sort_by_effective_priority );
    foreach ( $task_children_map as $parent_id => $child_ids )
    {
      usort( $child_ids, $sort_by_effective_priority );
      $task_children_map[ $parent_id ] = $child_ids;
    }

    $ordered_task_ids = [];
    $ordered_visited = [];
    $append_task_with_children = null;
    $append_task_with_children = function( $task_id ) use ( &$append_task_with_children, &$ordered_task_ids, &$ordered_visited, $task_children_map ) {
      if ( isset( $ordered_visited[ $task_id ] ) )
        return;

      $ordered_visited[ $task_id ] = true;
      $ordered_task_ids[] = $task_id;

      if ( isset( $task_children_map[ $task_id ] ) )
      {
        foreach ( $task_children_map[ $task_id ] as $child_id )
          $append_task_with_children( $child_id );
      }
    };

    foreach ( $root_ids as $root_id )
      $append_task_with_children( $root_id );

    foreach ( array_keys( $task_rows_by_id ) as $task_id )
      $append_task_with_children( $task_id );

    foreach ( $ordered_task_ids as $ordered_task_id ) {
      $task = $task_rows_by_id[ $ordered_task_id ];
      $effective_start = $task['date_start'] ? $task['date_start'] : $task['parent_date_start'];
      $effective_end = $task['date_end'] ? $task['date_end'] : $task['parent_date_end'];

      if ( !$effective_start and $effective_end )
        $effective_start = $effective_end;
      if ( !$effective_end and $effective_start )
        $effective_end = $effective_start;
      if ( !$effective_start or !$effective_end )
        continue;

      $task_depth = 0;
      $depth_parent_id = (int)$task['parent_id'];
      while ( $depth_parent_id > 0 and isset( $task_rows_by_id[ $depth_parent_id ] ) and $task_depth < 10 )
      {
        $task_depth++;
        $depth_parent_id = (int)$task_rows_by_id[ $depth_parent_id ]['parent_id'];
      }

      $task_json = [];
      $task_name = $task['client_id'] ? \factory\Crm::get_client_name( (int)$task['client_id'] ) . ' - ' . htmlspecialchars( $task['name'] ) : htmlspecialchars( $task['name'] );
      if ( $task_depth > 0 )
        $task_name = str_repeat( '  ', $task_depth ) . '-> ' . $task_name;

      $task_json['name'] = $task_name;
      // start
      $task_json['start'] = $effective_start;
      // end
      $task_json['end'] = $effective_end;
      // id
      $task_json['id'] = $task['id'];
      // custom class
      // if ( $task['date_start'] <= date( 'Y-m-d H:i:s' ) )
      //   $task_json['custom_class'] = 'gantt-task-backlog';
      $task_json['custom_class'] = '';

      if ( $task['parent_id'] )
        $task_json['dependencies'] = $task['parent_id'];

      if ( $task['status'] == 6 )
        $task_json['custom_class'] = 'gantt-task-faktura';

      if ( !$task_json['custom_class'] )
      {
        $display_priority = isset( $task_effective_priority[ (int)$task['id'] ] ) ? (int)$task_effective_priority[ (int)$task['id'] ] : (int)$task['priority'];
        $task_json['custom_class'] = 'gantt-task-priority-' . $display_priority;
      }

      // progress
      $task_json['progress'] = 0;

      $data[] = $task_json;
    }

    return $data;
  }

  static public function work_delete( $work_id ) {
    global $mdb;
    return $mdb -> update( 'tasks_work', [ 'deleted' => 1 ], [ 'id' => $work_id ] );
  }

  static public function change_task_work_date_end( $task_work_id, $date_end )
  {
    global $mdb;
    return $mdb -> update( 'tasks_work', [ 'date_end' => $date_end ? date( 'Y-m-d H:i:s', strtotime( $date_end ) ) : null ], [ 'id' => $task_work_id ] );
  }

  static public function change_task_work_date_start( $task_work_id, $date_start )
  {
    global $mdb;
    return $mdb -> update( 'tasks_work', [ 'date_start' => $date_start ? date( 'Y-m-d H:i:s', strtotime( $date_start ) ) : null ], [ 'id' => $task_work_id ] );
  }

  static public function task_works( $task_id )
  {
    global $mdb;
    return $mdb -> select( 'tasks_work', '*', [ 'AND' => [ 'task_id' => $task_id, 'deleted' => 0 ], 'ORDER' => [ 'date_end' => 'DESC' ] ] );
  }

  static public function get_statuses()
  {
    global $user;

    if ( $user['id'] == 1 )
      return self::$statuses;
    else
      return [ 0 => 'nowe', 3 => 'do rozliczenia', 5 => 'do zrobienia', 4 => 'zaplanowane', 1 => 'do sprawdzenia' ];
  }

  static public function clear_task_opened( $task_id )
  {
    global $mdb;
    return $mdb -> delete( 'tasks_opened', [ 'task_id' => $task_id ] );
  }

  static public function set_task_opened_by_user( $task_id, $user_id )
  {
    global $mdb;

    if ( $mdb -> count( 'tasks_opened', [ 'AND' => [ 'task_id' => $task_id, 'user_id' => $user_id ] ] ) )
      return false;

    return $mdb -> insert( 'tasks_opened', [
      'user_id' => $user_id,
      'task_id' => $task_id
    ] );
  }

  static public function is_taks_is_opened_by_user( $task_id, $user_id )
  {
    global $mdb;
    return $mdb -> count( 'tasks_opened', [ 'AND' => [ 'task_id' => $task_id, 'user_id' => $user_id ] ] );
  }

  static public function get_filtrs( $user_id ) {
    global $mdb;
    return $mdb -> select( 'tasks_filtrs', '*', [ 'user_id' => $user_id, 'ORDER' => [ 'name' => 'ASC' ] ] );
  }

  static public function filtr_update( $filtr_id, $projects, $users ) {
    global $mdb;
    return $mdb -> update( 'tasks_filtrs', [
      'projects' => $projects,
      'users' => $users
    ], [
      'id' => $filtr_id
    ] );
  }

  static public function filtr_save( $user_id, $name, $projects, $users, $is_default ) {
    global $mdb;

    if ( $is_default ) {
      $mdb -> update( 'tasks_filtrs', [ 'is_default' => 0 ], [ 'user_id' => $user_id ] );
    }

    $mdb -> insert( 'tasks_filtrs', [
      'user_id' => $user_id,
      'name' => $name,
      'projects' => $projects,
      'users' => $users,
      'is_default' => $is_default
    ] );

    return $mdb -> id();
  }

  static public function action_change_status( $action_id, $status ) {
    global $mdb;
    return $mdb -> update( 'task_action', [ 'status' => $status ], [ 'id' => $action_id ] );
  }

  static public function comment_delete( $comment_id ) {
    global $mdb;
    return $mdb -> delete( 'tasks_comments', [ 'id' => $comment_id ] );
  }

  static public function comment_save( $task_id, $user_id, $text )
  {
    global $mdb;

    if ( !$task_id or !$user_id or !$text )
      return false;

    return $mdb -> insert( 'tasks_comments', [
        'task_id' => $task_id,
        'user_id' => $user_id,
        'text' => $text,
        'date_add' => date( 'Y-m-d H:i:s' )
      ] );
  }

  static public function action_delete( $action_id )
  {
    global $mdb;
    return $mdb -> delete( 'task_action', [ 'id' => $action_id ] );
  }

  static public function action_save( $task_id, $text )
  {
    global $mdb;
    return $mdb -> insert( 'task_action', [
        'name' => $text,
        'task_id' => $task_id
      ] );
  }

  static public function get_tasks( int $status, $user_id, $projects = null, $users = null )
  {
    global $mdb;

    $tasks = [];

    if ( $status == 2 )
    {
      $sql = ' AND date_complete > \'' . date( 'Y-m-d', strtotime( "-2 months") ) . '\'';
    }

    if ( $projects )
    {
      $sql .= ' AND project_id IN (' . implode( ',', $projects ) . ')';
    }

    if ( $users )
    {
      $sql .= ' AND id IN (SELECT task_id FROM task_user WHERE user_id IN (' . implode( ',', $users ) . '))';
    }

    if ( $user_id == 1 )
    {
      if ( in_array( $status, [ 0, 2, 3 ] ) )
        $results = $mdb -> query( 'SELECT * FROM tasks WHERE status = ' . $status . ' ' . $sql . ' ORDER BY date_end IS NOT NULL DESC, date_end ASC, name ASC' ) -> fetchAll( \PDO::FETCH_ASSOC );
      elseif ( $status == 6 )
        $results = $mdb -> query( 'SELECT * FROM tasks WHERE status = ' . $status . ' ' . $sql . ' ORDER BY open DESC, date_end IS NOT NULL DESC, date_end ASC, name ASC' ) -> fetchAll( \PDO::FETCH_ASSOC );
      else
        $results = $mdb -> query( 'SELECT * FROM tasks WHERE status = ' . $status . ' ' . $sql . ' ORDER BY open DESC, o ASC, status ASC, date_start IS NOT NULL DESC, date_start ASC, date_end IS NOT NULL DESC, date_end ASC, name ASC' ) -> fetchAll( \PDO::FETCH_ASSOC );
    }
    else
    {
      if ( in_array( $status, [ 0, 2, 3 ] ) )
        $results = $mdb -> query( 'SELECT t.* FROM tasks t LEFT JOIN task_user tu ON t.id = tu.task_id WHERE t.status = ' . $status . ' AND tu.user_id = ' . $user_id . ' ' . $sql . ' ORDER BY t.date_end IS NOT NULL DESC, t.date_end ASC, t.name ASC' ) -> fetchAll( \PDO::FETCH_ASSOC );
      else
        $results = $mdb -> query( 'SELECT t.* FROM tasks t LEFT JOIN task_user tu ON t.id = tu.task_id WHERE t.status = ' . $status . ' AND tu.user_id = ' . $user_id . ' ' . $sql . ' ORDER BY t.open DESC, t.o ASC, t.status ASC, t.date_start IS NOT NULL DESC, t.date_start ASC, t.date_end IS NOT NULL DESC, t.date_end ASC, t.name ASC' ) -> fetchAll( \PDO::FETCH_ASSOC );
    }

    foreach ( $results as $row )
    {
      $row['users'] = $mdb -> select( 'task_user', 'user_id', [ 'task_id' => $row['id'] ] );
      $tasks[] = $row;
    }

    return $tasks;
  }

  static public function get_open_task_id( $user_id )
  {
    global $mdb;
    return $mdb -> get( 'tasks_work', 'task_id', [ 'AND' => [ 'user_id' => $user_id, 'date_end' => null ] ] );
  }

  static public function task_start( $task_id, $user_id )
  {
    global $mdb;

    $open_works = $mdb -> select( 'tasks_work', [ 'id', 'date_start' ], [
      'AND' => [
        'date_end' => null,
        'user_id' => $user_id,
        'deleted' => 0
      ]
    ] );

    if ( is_array( $open_works ) and count( $open_works ) )
    {
      $date_end_now = date( 'Y-m-d H:i:s' );

      foreach ( $open_works as $open_work )
      {
        $work_id = isset( $open_work['id'] ) ? (int)$open_work['id'] : 0;
        if ( !$work_id )
          continue;

        $is_too_short = self::is_work_duration_too_short( isset( $open_work['date_start'] ) ? $open_work['date_start'] : null, $date_end_now );

        $mdb -> update( 'tasks_work', [
          'date_end' => $date_end_now,
          'deleted' => $is_too_short ? 1 : 0
        ], [ 'id' => $work_id ] );
      }
    }

    $mdb -> insert( 'tasks_work', [
              'user_id'     => $user_id,
              'task_id'     => $task_id,
              'date_start'  => date( 'Y-m-d H:i:s' )
            ] );
    return [ 'status' => 'success' ];
  }

  static public function task_end( $task_id, $user_id )
  {
    global $mdb;

    $work = $mdb -> get( 'tasks_work', [ 'id', 'date_start' ], [
      'AND' => [
        'task_id' => $task_id,
        'user_id' => $user_id,
        'date_end' => null,
        'deleted' => 0
      ],
      'ORDER' => [ 'id' => 'DESC' ]
    ] );

    if ( !$work or !isset( $work['id'] ) )
      return [ 'status' => 'success' ];

    $date_end_now = date( 'Y-m-d H:i:s' );
    $is_too_short = self::is_work_duration_too_short( isset( $work['date_start'] ) ? $work['date_start'] : null, $date_end_now );

    $mdb -> update( 'tasks_work', [
      'date_end' => $date_end_now,
      'deleted' => $is_too_short ? 1 : 0
    ], [ 'id' => (int)$work['id'] ] );

    return [ 'status' => 'success' ];
  }

  private static function is_work_duration_too_short( $date_start, $date_end )
  {
    $start_timestamp = strtotime( (string)$date_start );
    $end_timestamp = strtotime( (string)$date_end );

    if ( $start_timestamp === false or $end_timestamp === false )
      return false;

    return ( $end_timestamp - $start_timestamp ) < self::MIN_WORK_LOG_SECONDS;
  }

  static public function task_details( $task_id, $user_id = null )
  {
    global $mdb;

    $task = $mdb -> get( 'tasks', '*', [ 'id' => $task_id ] );
    $task['users'] = $mdb -> select( 'task_user', 'user_id', [ 'task_id' => $task_id ] );
    $task['actions'] = $mdb -> select( 'task_action', '*', [ 'task_id' => $task_id, 'ORDER' => [ 'status' => 'ASC', 'id' => 'ASC' ] ] );
    $task['total_time'] = self::task_total_time( $task_id );
    if ( $user_id )
      $task['is_open'] = self::is_task_open( $task_id, $user_id );
    $task['comments'] = $mdb -> select( 'tasks_comments', '*', [ 'task_id' => $task_id, 'ORDER' => [ 'date_add' => 'DESC' ] ] );
    $task['wiki_ids'] = self::task_wiki_ids( $task_id );

    return $task;
  }

  private static function normalize_wiki_ids( $wiki_ids )
  {
    if ( $wiki_ids === null or $wiki_ids === '' )
      return [];

    if ( is_string( $wiki_ids ) and strpos( $wiki_ids, ',' ) !== false )
      $wiki_ids = explode( ',', $wiki_ids );

    if ( !is_array( $wiki_ids ) )
      $wiki_ids = [ $wiki_ids ];

    $result = [];
    foreach ( $wiki_ids as $wiki_id )
    {
      $wiki_id = (int)$wiki_id;
      if ( $wiki_id > 0 )
        $result[] = $wiki_id;
    }

    return array_values( array_unique( $result ) );
  }

  static public function task_wiki_ids( $task_id )
  {
    global $mdb;

    $task_id = (int)$task_id;
    if ( !$task_id )
      return [];

    $ids = $mdb -> select( 'task_wiki', 'wiki_id', [ 'task_id' => $task_id ] );
    if ( !is_array( $ids ) )
      return [];

    return array_values( array_unique( array_map( 'intval', $ids ) ) );
  }

  static public function set_task_wiki_links( $task_id, $wiki_ids, $user_id )
  {
    global $mdb;

    $task_id = (int)$task_id;
    $user_id = (int)$user_id;

    if ( !$task_id or !$user_id )
      return false;

    $wiki_ids = self::normalize_wiki_ids( $wiki_ids );
    $visible_wiki_ids = [];
    foreach ( $wiki_ids as $wiki_id )
    {
      if ( \factory\Wiki::is_category_visible_for_user( (int)$wiki_id, $user_id ) )
        $visible_wiki_ids[] = (int)$wiki_id;
    }

    $existing_ids = self::task_wiki_ids( $task_id );
    $hidden_existing_ids = [];
    foreach ( $existing_ids as $existing_id )
    {
      if ( !\factory\Wiki::is_category_visible_for_user( (int)$existing_id, $user_id ) )
        $hidden_existing_ids[] = (int)$existing_id;
    }

    $final_ids = array_values( array_unique( array_merge( $visible_wiki_ids, $hidden_existing_ids ) ) );

    $mdb -> delete( 'task_wiki', [ 'task_id' => $task_id ] );

    foreach ( $final_ids as $wiki_id )
      $mdb -> insert( 'task_wiki', [ 'task_id' => $task_id, 'wiki_id' => $wiki_id ] );

    return true;
  }

  static public function task_wiki_entries_for_user( $task_id, $user_id )
  {
    global $mdb;

    $task_id = (int)$task_id;
    $user_id = (int)$user_id;

    $response = [
      'all_count' => 0,
      'visible_count' => 0,
      'hidden_count' => 0,
      'entries' => []
    ];

    if ( !$task_id or !$user_id )
      return $response;

    $response['all_count'] = (int)$mdb -> count( 'task_wiki', [ 'task_id' => $task_id ] );

    if ( $user_id == 1 or $user_id == 3 )
      $sql = 'SELECT w.* FROM task_wiki tw INNER JOIN wiki w ON w.id = tw.wiki_id WHERE tw.task_id = ' . $task_id . ' ORDER BY w.name ASC';
    else
      $sql = 'SELECT DISTINCT w.* FROM task_wiki tw '
           . 'INNER JOIN wiki w ON w.id = tw.wiki_id '
           . 'LEFT JOIN wiki_users wu ON wu.wiki_id = w.id '
           . 'WHERE tw.task_id = ' . $task_id . ' AND (wu.user_id = ' . $user_id . ' OR NOT EXISTS (SELECT 1 FROM wiki_users wu2 WHERE wu2.wiki_id = w.id)) '
           . 'ORDER BY w.name ASC';

    $entries = $mdb -> query( $sql ) -> fetchAll( \PDO::FETCH_ASSOC );
    if ( !is_array( $entries ) )
      $entries = [];

    $response['entries'] = $entries;
    $response['visible_count'] = count( $entries );
    $response['hidden_count'] = max( 0, $response['all_count'] - $response['visible_count'] );

    return $response;
  }

  static public function task_total_time( $task_id, $month = '' )
  {
    global $mdb;

    $seconds = 0;

    if ( $month )
    {
      $results = $mdb -> select( 'tasks_work', [ 'date_start', 'date_end' ], [ 'AND' => [ 'deleted' => 0, 'task_id' => $task_id, 'date_end[>=]' => $month . '-01 00:00:00', 'date_end[<=]' => $month . '-' . date( 't', strtotime( $month ) ) . ' 23:59:59' ], 'ORDER' => [ 'id' => 'ASC' ] ] );
    }
    else
      $results = $mdb -> select( 'tasks_work', [ 'date_start', 'date_end' ], [ 'AND' => [ 'deleted' => 0, 'task_id' => $task_id ], 'ORDER' => [ 'id' => 'ASC' ] ] );

    if ( is_array( $results ) and count( $results ) ) foreach ( $results as $row )
    {
      if ( !$row['date_end'] )
        $row['date_end'] = date( 'Y-m-d H:i:s' );

      $seconds += strtotime( $row['date_end'] ) - strtotime( $row['date_start'] );
    }
    return $seconds;
  }

  static public function is_task_open( $task_id, $user_id )
  {
    global $mdb;

    if ( $mdb -> count( 'tasks_work', [ 'AND' => [ 'user_id' => $user_id, 'task_id' => $task_id, 'date_end' => null ] ] ) )
      return true;

    return false;
  }

  static public function work_time_clients()
  {
    $repository = new \Domain\Tasks\WorkTimeRepository();
    return $repository -> getClientsWithUnsettledTasks();
  }

  // przy zmianach pamiętać o zadaniach z CRON
  static public function task_save( $task_id, $parent_id = null, $recursive_parent_id = null, $user_id, $name, $text, $date_start, $date_end, $project_id, $client_id, $pay_rate, $reminders_interval, $recursively, $frequency, $period, $users, $date_end_month_day = null, $date_start_month_day = null, $send_email_notification = false, $status_change_mail, $rescursive = false, $status = 0, $show_in_calendar, $priority = 0, $recursive_last_date = null, $wiki_ids = [] )
  {
    global $mdb;

    if ( !$task_id )
    {
      $order = $mdb -> max( 'tasks', 'o', [ 'AND' => [ 'project_id' => $project_id, 'date_end[<=]' => $date_end ] ] );

      $mdb -> insert( 'tasks', [
        'created_by' => $user_id,
        'parent_id' => $parent_id,
        'recursive_parent_id' => $recursive_parent_id,
        'name' => $name,
        'text' => $rescursive ? $text : htmlspecialchars( $text ),
        'date_start' => $date_start != '' ? "$date_start" : null,
        'date_end' => $date_end != '' ? $date_end : null,
        'project_id' => $project_id,
        'client_id' => $client_id ? $client_id : null,
        'pay_rate' => ( $pay_rate != '' and $pay_rate > 0 ) ? $pay_rate : null,
        'reminders_interval' => $reminders_interval != '' ? $reminders_interval : null,
        'recursively' => self::isEnabled( $recursively ) ? 1 : 0,
        'frequency' => $frequency,
        'period' => $period,
        'date_end_month_day' => $date_end_month_day,
        'date_start_month_day' => $date_start_month_day,
        'status_change_mail' => self::isEnabled( $status_change_mail ) ? 1 : 0,
        'o' => ++$order,
        'status' => $status,
        'show_in_calendar' => self::isEnabled( $show_in_calendar ) ? 1 : 0,
        'priority' => $priority,
        'recursive_last_date' => $recursive_last_date != '' ? $recursive_last_date : null,
      ] );
      $id = $mdb -> id();

      /* uczestnicy */
      if ( is_array( $users ) and !empty( $users ) ) foreach ( $users as $user )
      {
        if ( $id and $user and !$mdb -> count( 'task_user', [ 'AND' => [ 'task_id' => (int)$id, 'user_id' => (int)$user ] ] ) )
        {
          if ( $mdb -> insert( 'task_user', [ 'task_id' => (int)$id, 'user_id' => (int)$user ] ) )
            if ( self::isEnabled( $send_email_notification ) )
              \factory\Projects::send_email_notification( $id, $user );
        }
      }
      else if ( $users and !empty( $users ) )
      {
        if ( $id and $users and !$mdb -> count( 'task_user', [ 'AND' => [ 'task_id' => (int)$id, 'user_id' => (int)$users ] ] ) )
         if ( $mdb -> insert( 'task_user', [ 'task_id' => (int)$id, 'user_id' => (int)$users ] ) );
          if ( self::isEnabled( $send_email_notification ) )
            \factory\Projects::send_email_notification( $id, $users );
      }

      self::set_task_wiki_links( $id, $wiki_ids, $user_id );
      return $id;
    }
    else
    {
      $mdb -> update( 'tasks', [
        'parent_id' => $parent_id,
        'recursive_parent_id' => $recursive_parent_id,
        'name' => $name,
        'text' => htmlspecialchars( $text ),
        'date_start' => $date_start != '' ? $date_start : null,
        'date_end' => $date_end != '' ? $date_end : null,
        'project_id' => $project_id,
        'client_id' => $client_id ? $client_id : null,
        'pay_rate' => ( $pay_rate != '' and $pay_rate > 0 ) ? $pay_rate : null,
        'reminders_interval' => $reminders_interval,
        'recursively' => self::isEnabled( $recursively ) ? 1 : 0,
        'frequency' => $frequency,
        'period' => $period,
        'reminders_send' => 0,
        'status_change_mail' => self::isEnabled( $status_change_mail ) ? 1 : 0,
        'status' => $status,
        'show_in_calendar' => self::isEnabled( $show_in_calendar ) ? 1 : 0,
        'priority' => $priority,
        'recursive_last_date' => $recursive_last_date != '' ? $recursive_last_date : null,
      ], [
        'AND' => [
          'id' => $task_id
        ]
      ] );

      /* uczestnicy */
      $mdb -> delete( 'task_user', [ 'task_id' => (int)$task_id ] );

      if ( is_array( $users ) and !empty( $users ) ) foreach ( $users as $user )
      {
        if ( $task_id and $user )
          $mdb -> insert( 'task_user', [ 'task_id' => (int)$task_id, 'user_id' => (int)$user ] );
      }
      else if ( !empty( $users ) )
      {
        if ( $task_id and $users )
          $mdb -> insert( 'task_user', [ 'task_id' => (int)$task_id, 'user_id' => (int)$users ] );
      }

      $mdb -> delete( 'tasks_reminders', [ 'task_id' => $task_id ] );
      self::set_task_wiki_links( (int)$task_id, $wiki_ids, $user_id );

      return $task_id;
    }
    return false;
  }

  static public function task_delete( $task_id ) {

    $task_first_id = \factory\Tasks::task_first_id( $task_id );
    \factory\Tasks::task_delete_all( $task_first_id );

    return true;
  }

  static public function task_first_id( $recursive_parent_id ) {
    global $mdb;

    if ( $task_id = $mdb -> get( 'tasks', 'recursive_parent_id', [ 'id' => $recursive_parent_id ] ) )
      return self::task_first_id( $task_id );
    else
      return $recursive_parent_id;
  }

  static public function task_delete_all( $task_id )
  {
    global $mdb;

    $recursive_parent_id = $mdb -> get( 'tasks', 'recursive_parent_id', [ 'id' => $task_id ] );

    if ( !$recursive_parent_id ) {
      $children_id = self::task_delete_from_db( $task_id );
      if ( $children_id )
        self::task_delete_all( $children_id );
    }

    if ( $recursive_parent_id )
      self::task_delete_all( $recursive_parent_id );
    else
      return false;
  }

  static public function task_delete_from_db( int $task_id )
  {
    global $mdb;

    $children = $mdb -> get( 'tasks', 'id', [ 'recursive_parent_id' => $task_id ] );

    $attachments_repository = new \Domain\Tasks\TaskAttachmentRepository( $mdb );
    $attachments_repository -> purgeByTaskId( $task_id );
    $mdb -> delete( 'task_wiki', [ 'task_id' => (int)$task_id ] );

    $mdb -> delete( 'tasks', [ 'id' => $task_id ] );
    $mdb -> update( 'tasks', [ 'recursive_parent_id' => null ], [ 'recursive_parent_id' => $task_id ] );

    return $children;
  }

  static public function filtr_set_default( $user_id, $filtr_id ) {
    global $mdb;

    $mdb -> update( 'tasks_filtrs', [ 'is_default' => 0 ], [ 'user_id' => $user_id ] );

    return $mdb -> update( 'tasks_filtrs', [ 'is_default' => 1 ], [ 'AND' => [ 'id' => $filtr_id, 'user_id' => $user_id ] ] );
  }

  static public function get_default_filtr( $user_id ) {
    global $mdb;
    return $mdb -> get( 'tasks_filtrs', '*', [ 'AND' => [ 'user_id' => $user_id, 'is_default' => 1 ] ] );
  }
}
?>
